SqlAlchemy 2.0 中文文档(七十五)(2)https://developer.aliyun.com/article/1562370
新特性和改进 - 核心
Engines 现在使连接无效,并为 BaseException 运行错误处理程序
版本 1.1 中的新内容:此更改是在 1.1 系列的 1.1 final 版本之前的最后添加,不包含在 1.1 beta 版本中。
Python 的BaseException类位于Exception之下,但是是诸如KeyboardInterrupt、SystemExit等系统级异常的可识别基类,特别是GreenletExit异常,该异常由 eventlet 和 gevent 使用。这个异常类现在被Connection的异常处理例程拦截,并且包括由ConnectionEvents.handle_error()事件处理。在不是Exception子类的系统级异常的情况下,默认情况下,Connection 被失效,因为假定操作被中断并且连接可能处于不可用状态。这个变化主要针对 MySQL 驱动程序,但是这个变化适用于所有的 DBAPIs。
注意,在失效时,Connection所使用的即时 DBAPI 连接被处理,并且如果在异常抛出后仍然在使用Connection,则在下次使用时将使用新的 DBAPI 连接进行后续操作;但是,正在进行中的任何事务的状态都会丢失,并且在重新使用之前,必须调用适当的.rollback()方法(如果适用)。
为了识别这种变化,很容易证明当这些异常发生在连接正在执行其工作时,一个 pymysql 或 mysqlclient/MySQL-Python 连接会进入一种已损坏的状态;然后,连接将被返回到连接池,在那里后续使用会失败,甚至在返回到池之前,在异常捕获时调用.rollback()的上下文管理器中会导致次要失败。这里的行为预计将减少 MySQL 错误“commands out of sync”的发生率,以及在 MySQL 驱动程序未能正确报告cursor.description时发生的ResourceClosedError,当在杀死 greenlet 的条件下运行时,在处理KeyboardInterrupt异常时不完全退出程序。
该行为与通常的自动失效功能不同,它不假设后端数据库本身已关闭或重新启动;对于通常的 DBAPI 断开异常情况,它不会重新循环整个连接池。
此更改应该对所有用户都是净改进,除了当前拦截KeyboardInterrupt或GreenletExit并希望在同一事务中继续工作的任何应用程序。这样的操作在其他不受KeyboardInterrupt影响的 DBAPI(如 psycopg2)中理论上是可能的。对于这些 DBAPI,以下解决方法将禁用特定异常时的连接重新循环:
engine = create_engine("postgresql+psycopg2://") @event.listens_for(engine, "handle_error") def cancel_disconnect(ctx): if isinstance(ctx.original_exception, KeyboardInterrupt): ctx.is_disconnect = False
#3803 ### CTE 支持 INSERT、UPDATE、DELETE
最广泛请求的功能之一是支持与 INSERT、UPDATE、DELETE 一起工作的通用表达式(CTE),现在已实现。INSERT/UPDATE/DELETE 可以从 SQL 顶部陈述的 WITH 子句中获取,也可以作为更大语句上下文中的 CTE 本身使用。
作为这一更改的一部分,包含 CTE 的 INSERT FROM SELECT 现在将在整个语句的顶部呈现 CTE,而不是像 1.0 版本中的 SELECT 语句中嵌套 CTE 那样。
以下是一个示例,它在一条语句中呈现了 UPDATE、INSERT 和 SELECT:
>>> from sqlalchemy import table, column, select, literal, exists >>> orders = table( ... "orders", ... column("region"), ... column("amount"), ... column("product"), ... column("quantity"), ... ) >>> >>> upsert = ( ... orders.update() ... .where(orders.c.region == "Region1") ... .values(amount=1.0, product="Product1", quantity=1) ... .returning(*(orders.c._all_columns)) ... .cte("upsert") ... ) >>> >>> insert = orders.insert().from_select( ... orders.c.keys(), ... select([literal("Region1"), literal(1.0), literal("Product1"), literal(1)]).where( ... ~exists(upsert.select()) ... ), ... ) >>> >>> print(insert) # Note: formatting added for clarity WITH upsert AS (UPDATE orders SET amount=:amount, product=:product, quantity=:quantity WHERE orders.region = :region_1 RETURNING orders.region, orders.amount, orders.product, orders.quantity ) INSERT INTO orders (region, amount, product, quantity) SELECT :param_1 AS anon_1, :param_2 AS anon_2, :param_3 AS anon_3, :param_4 AS anon_4 WHERE NOT ( EXISTS ( SELECT upsert.region, upsert.amount, upsert.product, upsert.quantity FROM upsert))
#2551 ### 对窗口函数内的 RANGE 和 ROWS 规范的支持
新的over.range_和over.rows参数允许 RANGE 和 ROWS 表达式用于窗口函数:
>>> from sqlalchemy import func >>> print(func.row_number().over(order_by="x", range_=(-5, 10))) row_number() OVER (ORDER BY x RANGE BETWEEN :param_1 PRECEDING AND :param_2 FOLLOWING) >>> print(func.row_number().over(order_by="x", rows=(None, 0))) row_number() OVER (ORDER BY x ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) >>> print(func.row_number().over(order_by="x", range_=(-2, None))) row_number() OVER (ORDER BY x RANGE BETWEEN :param_1 PRECEDING AND UNBOUNDED FOLLOWING)
over.range_ 和 over.rows 被指定为 2 元组,表示特定范围的负值和正值,0 表示“当前行”,None 表示无限制。
另请参阅
使用窗口函数
#3049 ### 支持 SQL 的 LATERAL 关键字
LATERAL 关键字目前仅被 PostgreSQL 9.3 及更高版本支持,但由于它是 SQL 标准的一部分,因此在 Core 中增加了对此关键字的支持。 Select.lateral() 的实现除了仅呈现 LATERAL 关键字之外,还采用了特殊逻辑,以允许从与可选择器相同的 FROM 子句派生的表进行相关联,例如横向相关性:
>>> from sqlalchemy import table, column, select, true >>> people = table("people", column("people_id"), column("age"), column("name")) >>> books = table("books", column("book_id"), column("owner_id")) >>> subq = ( ... select([books.c.book_id]) ... .where(books.c.owner_id == people.c.people_id) ... .lateral("book_subq") ... ) >>> print(select([people]).select_from(people.join(subq, true()))) SELECT people.people_id, people.age, people.name FROM people JOIN LATERAL (SELECT books.book_id AS book_id FROM books WHERE books.owner_id = people.people_id) AS book_subq ON true
另请参阅
LATERAL correlation
Lateral
Select.lateral()
#2857 ### 对 TABLESAMPLE 的支持
SQL 标准的 TABLESAMPLE 可以使用 FromClause.tablesample() 方法呈现,该方法返回一个类似于别名的 TableSample 构造:
from sqlalchemy import func selectable = people.tablesample(func.bernoulli(1), name="alias", seed=func.random()) stmt = select([selectable.c.people_id])
假设 people 有一个列 people_id,上述语句将渲染为:
SELECT alias.people_id FROM people AS alias TABLESAMPLE bernoulli(:bernoulli_1) REPEATABLE (random())
#3718 ### 对于复合主键列,不再隐式启用 .autoincrement 指令
SQLAlchemy 一直以来都具有便利功能,可以为单列整数主键启用后端数据库的“自动增量”功能;所谓“自动增量”是指数据库列将包括数据库提供的任何 DDL 指令,以指示自增长整数标识符,例如 PostgreSQL 上的 SERIAL 关键字或 MySQL 上的 AUTO_INCREMENT,并且此外,方言将使用适合于该后端的技术从执行 Table.insert() 构造中接收这些生成的值。
发生变化的是,此功能不再自动为 复合 主键打开;以前,表定义如下:
Table( "some_table", metadata, Column("x", Integer, primary_key=True), Column("y", Integer, primary_key=True), )
只会将autoincrement语义应用于'x'列,仅因为它是主键列列表中的第一个。为了禁用这个,必须关闭所有列上的autoincrement:
# old way Table( "some_table", metadata, Column("x", Integer, primary_key=True, autoincrement=False), Column("y", Integer, primary_key=True, autoincrement=False), )
使用新行为,除非列明确标记为autoincrement=True,否则复合主键不会具有自动增量语义:
# column 'y' will be SERIAL/AUTO_INCREMENT/ auto-generating Table( "some_table", metadata, Column("x", Integer, primary_key=True), Column("y", Integer, primary_key=True, autoincrement=True), )
为了预见一些潜在的不兼容情况,Table.insert()构造将对没有设置自动增量的复合主键列上缺失的主键值执行更彻底的检查;给定这样一个表:
Table( "b", metadata, Column("x", Integer, primary_key=True), Column("y", Integer, primary_key=True), )
当对此表进行无值插入时,会产生以下警告:
SAWarning: Column 'b.x' is marked as a member of the primary key for table 'b', but has no Python-side or server-side default generator indicated, nor does it indicate 'autoincrement=True', and no explicit value is passed. Primary key columns may not store NULL. Note that as of SQLAlchemy 1.1, 'autoincrement=True' must be indicated explicitly for composite (e.g. multicolumn) primary keys if AUTO_INCREMENT/SERIAL/IDENTITY behavior is expected for one of the columns in the primary key. CREATE TABLE statements are impacted by this change as well on most backends.
对于从服务器端默认值或者更少见的情况如触发器接收主键值的列,可以使用FetchedValue来指示值生成器的存在:
Table( "b", metadata, Column("x", Integer, primary_key=True, server_default=FetchedValue()), Column("y", Integer, primary_key=True, server_default=FetchedValue()), )
对于极少数情况下,复合主键实际上打算在其中一个或多个列中存储 NULL 的情况(仅在 SQLite 和 MySQL 上支持),请使用nullable=True指定列:
Table( "b", metadata, Column("x", Integer, primary_key=True), Column("y", Integer, primary_key=True, nullable=True), )
在相关更改中,autoincrement标志可以设置为 True,用于具有客户端或服务器端默认值的列。这通常不会对插入期间列的行为产生太大影响。
另请参见
不再为具有 AUTO_INCREMENT 的复合主键生成隐式 KEY
#3216 ### 支持 IS DISTINCT FROM 和 IS NOT DISTINCT FROM
新操作符ColumnOperators.is_distinct_from()和ColumnOperators.isnot_distinct_from()允许 IS DISTINCT FROM 和 IS NOT DISTINCT FROM sql 操作:
>>> print(column("x").is_distinct_from(None)) x IS DISTINCT FROM NULL
处理 NULL、True 和 False:
>>> print(column("x").isnot_distinct_from(False)) x IS NOT DISTINCT FROM false
对于 SQLite,它没有这个运算符,“IS” / “IS NOT”会被渲染,这在 SQLite 上可以用于 NULL,不像其他后端:
>>> from sqlalchemy.dialects import sqlite >>> print(column("x").is_distinct_from(None).compile(dialect=sqlite.dialect())) x IS NOT NULL ```### Core 和 ORM 支持 FULL OUTER JOIN 新标志`FromClause.outerjoin.full`,在 Core 和 ORM 级别可用,指示编译器在通常渲染`LEFT OUTER JOIN`的地方渲染`FULL OUTER JOIN`: ```py stmt = select([t1]).select_from(t1.outerjoin(t2, full=True))
该标志也适用于 ORM 级别:
q = session.query(MyClass).outerjoin(MyOtherClass, full=True)
#1957 ### ResultSet 列匹配增强;文本 SQL 的位置列设置
在 1.0 系列中对 ResultProxy 系统进行了一系列改进,作为 #918 的一部分,它重新组织了内部,使游标绑定的结果列与表/ORM 元数据按位置匹配,而不是按名称匹配,用于包含有关要返回的结果行的完整信息的编译 SQL 构造。这允许大大节省 Python 开销,并且在将 ORM 和 Core SQL 表达式链接到结果行时具有更高的准确性。在 1.1 版本中,此重新组织已进一步在内部进行,并且还通过最近添加的 TextClause.columns() 方法可用于纯文本 SQL 构造。
TextAsFrom.columns() 现在按位置工作
TextClause.columns() 方法在 0.9 版本中新增,接受基于列的参数位置;在 1.1 版本中,当所有列都按位置传递时,这些列与最终结果集的关联也将按位置执行。这里的关键优势在于,现在可以将文本 SQL 链接到 ORM 级别的结果集,而无需处理模糊或重复的列名称,也无需匹配标签方案到 ORM 级别的标签方案。现在所需的只是在文本 SQL 中以及传递给 TextClause.columns() 的列参数内部的相同列顺序:
from sqlalchemy import text stmt = text( "SELECT users.id, addresses.id, users.id, " "users.name, addresses.email_address AS email " "FROM users JOIN addresses ON users.id=addresses.user_id " "WHERE users.id = 1" ).columns(User.id, Address.id, Address.user_id, User.name, Address.email_address) query = session.query(User).from_statement(stmt).options(contains_eager(User.addresses)) result = query.all()
在上面的文本 SQL 中,“id” 列重复出现了三次,这通常是不明确的。使用新功能,我们可以直接应用来自 User 和 Address 类的映射列,甚至将 Address.user_id 列链接到文本 SQL 中的 users.id 列以供娱乐,而 Query 对象将收到正确的可按需定位的行,包括用于急加载。
这种更改与使用不同顺序将列传递给方法的代码不兼容。希望由于这种方法一直以来都是按照文本 SQL 语句中列的相同顺序传递列的方式来记录的,因此其影响将会很小,即使内部未进行此检查也是如此。无论如何,该方法仅从 0.9 版本开始添加,可能尚未被广泛使用。有关如何处理使用此方法的应用程序的行为更改的详细说明,请参见当传递列位置性地传递时,TextClause.columns() 将不按名称匹配列。
另请参阅
使用文本列表达式进行选择
当传递位置参数时,
TextClause.columns()将按位置而不是按名称匹配列 - 向后兼容说明
位置匹配优先于基于名称的匹配用于 Core/ORM SQL 构造
此更改的另一个方面是对于编译后的 SQL 构造,匹配列的规则也已经修改,更充分地依赖于“位置”匹配。给定以下语句:
ua = users.alias("ua") stmt = select([users.c.user_id, ua.c.user_id])
上述语句将编译为:
SELECT users.user_id, ua.user_id FROM users, users AS ua
在 1.0 中,当执行上述语句时,将使用位置匹配来匹配其原始编译结构,但是因为该语句包含重复的'user_id'标签,所以“模糊列”规则仍然会涉及并阻止从行中获取列。从 1.1 开始,“模糊列”规则不会影响从列构造到 SQL 列的精确匹配,这是 ORM 用于获取列的方式:
result = conn.execute(stmt) row = result.first() # these both match positionally, so no error user_id = row[users.c.user_id] ua_id = row[ua.c.user_id] # this still raises, however user_id = row["user_id"]
很少会收到“模糊列”错误消息
作为此更改的一部分,错误消息结果集中的模糊列名''!尝试在 select 语句上使用 'use_labels' 选项。的措辞已经有所减少;因为现在当使用 ORM 或 Core 编译后的 SQL 构造时,这个消息应该极其罕见,所以它只是简单地陈述了结果集列描述中的模糊列名'',仅当使用实际模糊的名称从渲染的 SQL 语句中检索结果列时,例如上面的row['user_id']。它现在还引用了来自渲染的 SQL 语句本身的实际模糊名称,而不是指示用于获取的构造本地的键或名称。
#3501 ### 支持 Python 的原生enum类型及兼容形式
Enum类型现在可以使用任何符合 PEP-435 的枚举类型构造。在使用此模式时,输入值和返回值是实际的枚举对象,而不是字符串/整数等值:
import enum from sqlalchemy import Table, MetaData, Column, Enum, create_engine class MyEnum(enum.Enum): one = 1 two = 2 three = 3 t = Table("data", MetaData(), Column("value", Enum(MyEnum))) e = create_engine("sqlite://") t.create(e) e.execute(t.insert(), {"value": MyEnum.two}) assert e.scalar(t.select()) is MyEnum.two
Enum.enums集合现在是一个列表而不是一个元组
作为对 Enum 的更改的一部分,Enum.enums元素的集合现在是一个列表而不是一个元组。这是因为列表适用于长度可变的同类项序列,其中元素的位置没有语义上的重要性。
#3292 ### 核心结果行支持负整数索引
RowProxy对象现在像常规的 Python 序列一样支持单个负整数索引,无论是在纯 Python 版本还是在 C 扩展版本中。以前,负值仅在切片中起作用:
>>> from sqlalchemy import create_engine >>> e = create_engine("sqlite://") >>> row = e.execute("select 1, 2, 3").first() >>> row[-1], row[-2], row[1], row[-2:2] 3 2 2 (2,) ```### `Enum`类型现在在 Python 中对值进行验证 为了适应 Python 本机枚举对象,以及诸如在 ARRAY 中使用非本地 ENUM 类型且 CHECK 约束不可行等边缘情况,当使用`Enum.validate_strings`标志时,`Enum`数据类型现在在 Python 端验证输入值(1.1.0b2): ```py >>> from sqlalchemy import Table, MetaData, Column, Enum, create_engine >>> t = Table( ... "data", ... MetaData(), ... Column("value", Enum("one", "two", "three", validate_strings=True)), ... ) >>> e = create_engine("sqlite://") >>> t.create(e) >>> e.execute(t.insert(), {"value": "four"}) Traceback (most recent call last): ... sqlalchemy.exc.StatementError: (exceptions.LookupError) "four" is not among the defined enum values [SQL: u'INSERT INTO data (value) VALUES (?)'] [parameters: [{'value': 'four'}]]
默认情况下,此验证是关闭的,因为已经确定了用户不希望进行此类验证的用例(如字符串比较)。对于非字符串类型,它在所有情况下必须进行。当从数据库返回值时,结果处理方面的检查也是无条件发生的。
这种验证是在使用非本地枚举类型时创建 CHECK 约束的现有行为之外的。现在可以使用新的Enum.create_constraint标志禁用此 CHECK 约束的创建。
#3095 ### 非本地布尔整数值在所有情况下被强制转换为零/一/None
Boolean数据类型将 Python 布尔值强制转换为整数值,用于那些没有本地布尔类型的后端,如 SQLite 和 MySQL。在这些后端,通常设置一个 CHECK 约束,以确保数据库中的值实际上是这两个值之一。然而,MySQL 忽略 CHECK 约束,该约束是可选的,现有数据库可能没有这个约束。Boolean数据类型已修复,使得已经是整数值的 Python 端值被强制转换为零或一,而不仅仅是原样传递;此外,结果的 C 扩展版本的整数到布尔处理器现在使用与 Python 布尔值解释相同的值,而不是断言一个确切的一或零值。这现在与纯 Python 整数到布尔处理器一致,并且对数据库中已有的数据更宽容。None/NULL 值仍然保留为 None/NULL。
注意
此更改意外地导致非整数值(例如字符串)的解释行为也发生了更改,使得字符串值 "0" 被解释为“true”,但仅适用于没有本机布尔数据类型的后端 - 在“本机布尔”后端(如 PostgreSQL)上,字符串值 "0" 直接传递给驱动程序,并解释为“false”。这是以前实现中没有发生的不一致性。应注意,将字符串或任何其他值传递给 Boolean 数据类型外的值 None、True、False、1、0 是 不受支持 的,版本 1.2 将为此场景引发错误(或可能只是发出警告,待定)。另请参阅 #4102。
#3730 ### 在日志和异常显示中,现在会截断大参数和行值
在 SQL 语句的绑定参数中存在大值,以及在结果行中存在大值,现在在日志记录、异常报告以及行本身的 repr() 中都将被截断显示:
>>> from sqlalchemy import create_engine >>> import random >>> e = create_engine("sqlite://", echo="debug") >>> some_value = "".join(chr(random.randint(52, 85)) for i in range(5000)) >>> row = e.execute("select ?", [some_value]).first() ... # (lines are wrapped for clarity) ... 2016-02-17 13:23:03,027 INFO sqlalchemy.engine.base.Engine select ? 2016-02-17 13:23:03,027 INFO sqlalchemy.engine.base.Engine ('E6@?>9HPOJB<<BHR:@=TS:5ILU=;JLM<4?B9<S48PTNG9>:=TSTLA;9K;9FPM4M8M@;NM6GU LUAEBT9QGHNHTHR5EP75@OER4?SKC;D:TFUMD:M>;C6U:JLM6R67GEK<A6@S@C@J7>4=4:P GJ7HQ6 ... (4702 characters truncated) ... J6IK546AJMB4N6S9L;;9AKI;=RJP HDSSOTNBUEEC9@Q:RCL:I@5?FO<9K>KJAGAO@E6@A7JI8O:J7B69T6<8;F:S;4BEIJS9HM K:;5OLPM@JR;R:J6<SOTTT=>Q>7T@I::OTDC:CC<=NGP6C>BC8N',) 2016-02-17 13:23:03,027 DEBUG sqlalchemy.engine.base.Engine Col ('?',) 2016-02-17 13:23:03,027 DEBUG sqlalchemy.engine.base.Engine Row (u'E6@?>9HPOJB<<BHR:@=TS:5ILU=;JLM<4?B9<S48PTNG9>:=TSTLA;9K;9FPM4M8M@; NM6GULUAEBT9QGHNHTHR5EP75@OER4?SKC;D:TFUMD:M>;C6U:JLM6R67GEK<A6@S@C@J7 >4=4:PGJ7HQ ... (4703 characters truncated) ... J6IK546AJMB4N6S9L;;9AKI;= RJPHDSSOTNBUEEC9@Q:RCL:I@5?FO<9K>KJAGAO@E6@A7JI8O:J7B69T6<8;F:S;4BEIJS9HM K:;5OLPM@JR;R:J6<SOTTT=>Q>7T@I::OTDC:CC<=NGP6C>BC8N',) >>> print(row) (u'E6@?>9HPOJB<<BHR:@=TS:5ILU=;JLM<4?B9<S48PTNG9>:=TSTLA;9K;9FPM4M8M@;NM6 GULUAEBT9QGHNHTHR5EP75@OER4?SKC;D:TFUMD:M>;C6U:JLM6R67GEK<A6@S@C@J7>4 =4:PGJ7HQ ... (4703 characters truncated) ... J6IK546AJMB4N6S9L;;9AKI; =RJPHDSSOTNBUEEC9@Q:RCL:I@5?FO<9K>KJAGAO@E6@A7JI8O:J7B69T6<8;F:S;4BEIJS9H MK:;5OLPM@JR;R:J6<SOTTT=>Q>7T@I::OTDC:CC<=NGP6C>BC8N',)
#2837 ### Core 添加了 JSON 支持
由于 MySQL 现在除了 PostgreSQL JSON 数据类型外还有 JSON 数据类型,核心现在获得了一个 sqlalchemy.types.JSON 数据类型,它是这两者的基础。使用此类型允许以 PostgreSQL 和 MySQL 通用的方式访问“getitem”操作符和“getpath”操作符。
新数据类型还对 NULL 值的处理以及表达式处理进行了一系列改进。
另请参阅
MySQL JSON 支持
JSON
JSON
JSON
使用 ORM 操作时,“null” 会如预期地插入,当不存在时则被省略
JSON 类型及其子类型 JSON 和 JSON 具有一个标志 JSON.none_as_null,当设置为 True 时表示 Python 值 None 应该转换为 SQL NULL 而不是 JSON NULL 值。此标志默认为 False,这意味着 Python 值 None 应该导致 JSON NULL 值。
在以下情况下,此逻辑将失败,现在已进行更正:
1. 当列还包含默认值或server_default值时,对预期持久化 JSON “null” 的映射属性上的None的正值仍将触发列级默认值,替换None值:
class MyObject(Base): # ... json_value = Column(JSON(none_as_null=False), default="some default") # would insert "some default" instead of "'null'", # now will insert "'null'" obj = MyObject(json_value=None) session.add(obj) session.commit()
2. 当列没有包含默认值或server_default值时,在配置了none_as_null=False的 JSON 列上的缺失值仍将呈现 JSON NULL,而不是回退到不插入任何值,这与所有其他数据类型的行为不一致:
class MyObject(Base): # ... some_other_value = Column(String(50)) json_value = Column(JSON(none_as_null=False)) # would result in NULL for some_other_value, # but json "'null'" for json_value. Now results in NULL for both # (the json_value is omitted from the INSERT) obj = MyObject() session.add(obj) session.commit()
这是一种行为变更,对于依赖此行为将缺失值默认为 JSON null 的应用程序而言,这是不兼容的。这基本上表明缺失值与存在的None值是有区别的。有关更多详细信息,请参见 JSON Columns will not insert JSON NULL if no value is supplied and no default is established。
3. 当使用Session.bulk_insert_mappings()方法时,无论何种情况下都会忽略None:
# would insert SQL NULL and/or trigger defaults, # now inserts "'null'" session.bulk_insert_mappings(MyObject, [{"json_value": None}])
JSON类型现在实现了TypeEngine.should_evaluate_none标志,指示此处不应忽略None;它会根据JSON.none_as_null的值自动配置。感谢 #3061,我们可以区分用户主动设置None值和根本没有设置的情况。
该特性同样适用于新的基本JSON类型及其后代类型。
#3514 #### 新增 JSON.NULL 常量
为了确保应用程序始终可以完全控制一个 JSON、JSON、JSON 或 JSONB 列是否接收 SQL NULL 或 JSON "null" 值,添加了常量 JSON.NULL,它与 null() 结合使用,可以完全确定 SQL NULL 和 JSON "null" 之间的区别,不管 JSON.none_as_null 设置为何值:
from sqlalchemy import null from sqlalchemy.dialects.postgresql import JSON obj1 = MyObject(json_value=null()) # will *always* insert SQL NULL obj2 = MyObject(json_value=JSON.NULL) # will *always* insert JSON string "null" session.add_all([obj1, obj2]) session.commit()
这个功能同样适用于新的基础 JSON 类型及其派生类型。
#3514 ### Core 中添加了数组支持;新的 ANY 和 ALL 运算符
除了对 PostgreSQL ARRAY 类型所做的增强描述在 通过数组、JSON、HSTORE 的索引访问建立正确的 SQL 类型 中,ARRAY 的基类本身已经移动到 Core 中,成为一个新的类 ARRAY。
数组是 SQL 标准的一部分,还有一些面向数组的函数,比如 array_agg() 和 unnest()。为了支持这些构造不仅仅是针对 PostgreSQL,还有可能是未来其他支持数组的后端,比如 DB2,现在大部分 SQL 表达式的数组逻辑都在 Core 中。ARRAY 类型仍然只在 PostgreSQL 上工作,但可以直接使用,支持特殊的数组用例,比如索引访问,以及支持 ANY 和 ALL:
mytable = Table("mytable", metadata, Column("data", ARRAY(Integer, dimensions=2))) expr = mytable.c.data[5][6] expr = mytable.c.data[5].any(12)
为了支持 ANY 和 ALL,ARRAY 类型保留了与 PostgreSQL 类型相同的 Comparator.any() 和 Comparator.all() 方法,但也将这些操作导出为新的独立运算符函数 any_() 和 all_()。这两个函数以更传统的 SQL 方式工作,允许右侧表达式形式,如:
from sqlalchemy import any_, all_ select([mytable]).where(12 == any_(mytable.c.data[5]))
对于 PostgreSQL 特定的运算符“contains”,“contained_by” 和 “overlaps”,应继续直接使用 ARRAY 类型,该类型还提供了 ARRAY 类型的所有功能。
any_() 和 all_() 运算符在核心层面是开放的,但是后端数据库对它们的解释是有限的。在 PostgreSQL 后端,这两个运算符只接受数组值。而在 MySQL 后端,它们只接受子查询值。在 MySQL 中,可以使用如下表达式:
from sqlalchemy import any_, all_ subq = select([mytable.c.value]) select([mytable]).where(12 > any_(subq))
#3516 ### 新功能特性,“WITHIN GROUP”,array_agg 和 set 聚合函数
使用新的 ARRAY 类型,我们还可以实现一个预定义的函数,用于返回一个数组的 array_agg() SQL 函数,现在可以使用 array_agg:
from sqlalchemy import func stmt = select([func.array_agg(table.c.value)])
通过 aggregate_order_by 还添加了一个用于聚合 ORDER BY 的 PostgreSQL 元素��
from sqlalchemy.dialects.postgresql import aggregate_order_by expr = func.array_agg(aggregate_order_by(table.c.a, table.c.b.desc())) stmt = select([expr])
产生:
SELECT array_agg(table1.a ORDER BY table1.b DESC) AS array_agg_1 FROM table1
PG 方言本身还提供了一个 array_agg() 包装器,以确保 ARRAY 类型:
from sqlalchemy.dialects.postgresql import array_agg stmt = select([array_agg(table.c.value).contains("foo")])
此外,像 percentile_cont()、percentile_disc()、rank()、dense_rank() 等需要通过 WITHIN GROUP (ORDER BY ) 进行排序的函数现在可以通过 FunctionElement.within_group() 修改器使用:
from sqlalchemy import func stmt = select( [ department.c.id, func.percentile_cont(0.5).within_group(department.c.salary.desc()), ] )
上述语句会产生类似于以下 SQL:
SELECT department.id, percentile_cont(0.5) WITHIN GROUP (ORDER BY department.salary DESC)
现在为这些函数提供了正确返回类型的占位符,并包括 percentile_cont、percentile_disc、rank、dense_rank、mode、percent_rank 和 cume_dist。
#3132 #1370 ### TypeDecorator 现在自动与 Enum、Boolean、“schema” 类型一起工作
SchemaType 类型包括诸如 Enum 和 Boolean 等类型,除了对应数据库类型外,还会自动生成 CHECK 约束或者在 PostgreSQL ENUM 的情况下生成新的 CREATE TYPE 语句,现在可以自动与 TypeDecorator 配方一起使用了。以前,ENUM 的 TypeDecorator 需要像这样:
# old way class MyEnum(TypeDecorator, SchemaType): impl = postgresql.ENUM("one", "two", "three", name="myenum") def _set_table(self, table): self.impl._set_table(table)
TypeDecorator 现在传播这些额外的事件,所以它可以像任何其他类型一样进行操作:
# new way class MyEnum(TypeDecorator): impl = postgresql.ENUM("one", "two", "three", name="myenum")
#2919 ### 多租户模式下的 Table 对象的架构翻译
为了支持一个应用程序使用许多模式中相同的一组 Table 对象的用例,例如每个用户一个模式,添加了一个新的执行选项 Connection.execution_options.schema_translate_map。 使用这个映射,一组 Table 对象可以在每个连接基础上被制作,以引用任何一组模式,而不是它们被分配到的 Table.schema。 该翻译适用于 DDL 和 SQL 生成,以及与 ORM 一起使用。
例如,如果 User 类被分配了模式“per_user”:
class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True) __table_args__ = {"schema": "per_user"}
在每个请求上,Session 可以设置为每次引用不同的模式:
session = Session() session.connection( execution_options={"schema_translate_map": {"per_user": "account_one"}} ) # will query from the ``account_one.user`` table session.query(User).get(5)
另请参阅
模式名称的翻译
#2685 ### “友好”的 Core SQL 构造的字符串化,没有方言
对 Core SQL 构造调用 str() 现在会在更多情况下产生一个字符串,支持各种通常不在默认 SQL 中出现的 SQL 构造,如 RETURNING、数组索引和非标准数据类型:
>>> from sqlalchemy import table, column t>>> t = table('x', column('a'), column('b')) >>> print(t.insert().returning(t.c.a, t.c.b)) INSERT INTO x (a, b) VALUES (:a, :b) RETURNING x.a, x.b
str() 函数现在调用一个完全独立的方言/编译器,仅用于普通字符串打印而没有特定的方言设置,因此当出现更多“只是显示给我一个字符串!”的情况时,这些可以添加到这个方言/编译器中,而不会影响真实方言上的行为。
另请参阅
查询的字符串化将咨询 Session 获取正确的方言
#3631 ### type_coerce 函数现在是一个持久的 SQL 元素
type_coerce() 函数以前会返回一个对象,要么是类型为 BindParameter,要么是类型为 Label,取决于输入。 这将产生的影响是,在使用表达式转换的情况下,例如将元素从 Column 转换为 BindParameter,这对 ORM 级别的延迟加载至关重要,类型强制转换信息将不会被使用,因为它已经丢失了。
为了改进这种行为,该函数现在返回一个围绕给定表达式的持久TypeCoerce容器,该表达式本身保持不变;这个结构会被 SQL 编译器显式评估。这样可以确保内部表达式的强制转换在语句如何修改时都能保持不变,包括如果包含的元素被替换为另一个元素,这在 ORM 的延迟加载功能中很常见。
展示效果的测试案例利用了异构的 primaryjoin 条件,结合自定义类型和延迟加载。给定一个应用 CAST 作为“绑定表达式”的自定义类型:
class StringAsInt(TypeDecorator): impl = String def column_expression(self, col): return cast(col, Integer) def bind_expression(self, value): return cast(value, String)
然后,一个映射,我们将一个表上的字符串 “id” 列与另一个表上的整数 “id” 列进行等价比较:
class Person(Base): __tablename__ = "person" id = Column(StringAsInt, primary_key=True) pets = relationship( "Pets", primaryjoin=( "foreign(Pets.person_id)==cast(type_coerce(Person.id, Integer), Integer)" ), ) class Pets(Base): __tablename__ = "pets" id = Column("id", Integer, primary_key=True) person_id = Column("person_id", Integer)
在上面的 relationship.primaryjoin 表达式中,我们使用 type_coerce() 处理通过延迟加载传递的绑定参数作为整数,因为我们已经知道这些参数将来自我们的 StringAsInt 类型,该类型在 Python 中保持为整数值。然后我们使用 cast(),以便作为 SQL 表达式,VARCHAR “id” 列将被 CAST 为整数,用于常规非转换连接,如 Query.join() 或 joinedload()。也就是说,.pets 的 joinedload 如下所示:
SELECT person.id AS person_id, pets_1.id AS pets_1_id, pets_1.person_id AS pets_1_person_id FROM person LEFT OUTER JOIN pets AS pets_1 ON pets_1.person_id = CAST(person.id AS INTEGER)
在连接的 ON 子句中没有 CAST,像 PostgreSQL 这样的强类型数据库将拒绝隐式比较整数并失败。
.pets 的延迟加载情况依赖于在加载时用绑定参数替换 Person.id 列,该参数接收一个 Python 加载的值。这种替换是我们的 type_coerce() 函数意图会丢失的具体地方。在更改之前,这种延迟加载如下所示:
SELECT pets.id AS pets_id, pets.person_id AS pets_person_id FROM pets WHERE pets.person_id = CAST(CAST(%(param_1)s AS VARCHAR) AS INTEGER) -- {'param_1': 5}
在上面的例子中,我们看到我们在 Python 中的值 5 首先被 CAST 为 VARCHAR,然后在 SQL 中再次被 CAST 为 INTEGER;这是一个双重 CAST,虽然有效,但并不是我们要求的。
随着这一变化,type_coerce()函数在列被替换为绑定参数后仍保持一个包装器,查询现在看起来像这样:
SELECT pets.id AS pets_id, pets.person_id AS pets_person_id FROM pets WHERE pets.person_id = CAST(%(param_1)s AS INTEGER) -- {'param_1': 5}
我们主要连接中的外部 CAST 仍然生效,但是StringAsInt自定义类型的不必要的 CAST 部分已经被type_coerce()函数按预期移除。
#3531 ### 引擎现在使连接无效,运行 BaseException 的错误处理程序
新版本 1.1 中新增:这个变化是在 1.1 最终版本之前的 1.1 系列中添加的,不包含在 1.1 beta 版本中。
Python 的 BaseException 类位于 Exception 之下,但是是系统级异常(如KeyboardInterrupt、SystemExit,尤其是由 eventlet 和 gevent 使用的GreenletExit异常)的可识别基类。现在,Connection 的异常处理例程拦截了这个异常类,并包括由 ConnectionEvents.handle_error() 事件处理。在不是Exception子类的系统级异常的情况下,默认情况下现在会使连接无效,因为假定操作被中断,连接可能处于不可用状态。这个变化主要针对 MySQL 驱动程序,但是这个变化适用于所有 DBAPI。
在无效化时,Connection 使用的即时 DBAPI 连接会被释放,如果在异常抛出后仍在使用 Connection,则在下次使用时会使用新的 DBAPI 连接进行后续操作;然而,任何正在进行中的事务状态都会丢失,必须在重新使用之前调用适当的.rollback()方法(如果适用)。
为了识别这种变化,很容易证明当这些异常发生在连接正在工作时,pymysql 或 mysqlclient / MySQL-Python 连接会进入损坏状态;然后连接将被返回到连接池,后续使用将失败,甚至在返回到池之前会导致在调用.rollback()的上下文管理器中引发次要故障。这里的行为预期将减少 MySQL 错误“commands out of sync”的发生,以及在 MySQL 驱动程序未能正确报告cursor.description时可能发生的ResourceClosedError,当在 greenlet 条件下运行时,其中 greenlets 被终止,或者处理KeyboardInterrupt异常而不完全退出程序时。
这种行为与通常的自动失效功能不同,它不假设后端数据库本身已关闭或重新启动;它不像通常的 DBAPI 断开异常那样重新生成整个连接池。
这个改变应该对所有用户都是一个净改进,除了任何当前拦截KeyboardInterrupt或GreenletExit并希望在同一事务中继续工作的应用程序。对于那些理论上不受KeyboardInterrupt影响的其他 DBAPIs,比如 psycopg2,这样的操作是可能的。对于这些 DBAPIs,以下解决方法将禁用特定异常的连接被回收:
engine = create_engine("postgresql+psycopg2://") @event.listens_for(engine, "handle_error") def cancel_disconnect(ctx): if isinstance(ctx.original_exception, KeyboardInterrupt): ctx.is_disconnect = False
支持 INSERT、UPDATE、DELETE 的 CTE
最广泛请求的功能之一是支持与 INSERT、UPDATE、DELETE 一起工作的通用表达式(CTE),现在已经实现。INSERT/UPDATE/DELETE 可以从位于 SQL 顶部的 WITH 子句中提取,也可以作为更大语句上下文中的 CTE 使用。
作为这个改变的一部分,包含 CTE 的 INSERT FROM SELECT 现在将在整个语句的顶部呈现 CTE,而不是像 1.0 中那样嵌套在 SELECT 语句中。
下面是一个在一个语句中呈现 UPDATE、INSERT 和 SELECT 的示例:
>>> from sqlalchemy import table, column, select, literal, exists >>> orders = table( ... "orders", ... column("region"), ... column("amount"), ... column("product"), ... column("quantity"), ... ) >>> >>> upsert = ( ... orders.update() ... .where(orders.c.region == "Region1") ... .values(amount=1.0, product="Product1", quantity=1) ... .returning(*(orders.c._all_columns)) ... .cte("upsert") ... ) >>> >>> insert = orders.insert().from_select( ... orders.c.keys(), ... select([literal("Region1"), literal(1.0), literal("Product1"), literal(1)]).where( ... ~exists(upsert.select()) ... ), ... ) >>> >>> print(insert) # Note: formatting added for clarity WITH upsert AS (UPDATE orders SET amount=:amount, product=:product, quantity=:quantity WHERE orders.region = :region_1 RETURNING orders.region, orders.amount, orders.product, orders.quantity ) INSERT INTO orders (region, amount, product, quantity) SELECT :param_1 AS anon_1, :param_2 AS anon_2, :param_3 AS anon_3, :param_4 AS anon_4 WHERE NOT ( EXISTS ( SELECT upsert.region, upsert.amount, upsert.product, upsert.quantity FROM upsert))
支持窗口函数中的 RANGE 和 ROWS 规范
新的over.range_和over.rows参数允许窗口函数使用 RANGE 和 ROWS 表达式:
>>> from sqlalchemy import func >>> print(func.row_number().over(order_by="x", range_=(-5, 10))) row_number() OVER (ORDER BY x RANGE BETWEEN :param_1 PRECEDING AND :param_2 FOLLOWING) >>> print(func.row_number().over(order_by="x", rows=(None, 0))) row_number() OVER (ORDER BY x ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) >>> print(func.row_number().over(order_by="x", range_=(-2, None))) row_number() OVER (ORDER BY x RANGE BETWEEN :param_1 PRECEDING AND UNBOUNDED FOLLOWING)
over.range_和over.rows被指定为 2 元组,表示特定范围的负值和正值,“CURRENT ROW”为 0,UNBOUNDED 为 None。
另请参见
使用窗口函数
支持 SQL 的 LATERAL 关键字
LATERAL 关键字目前只被已知支持 PostgreSQL 9.3 及更高版本,然而,由于它是 SQL 标准的一部分,因此 Core 添加了对该关键字的支持。Select.lateral()的实现采用了特殊逻辑,不仅仅是呈现 LATERAL 关键字,还允许来自与可选择器相同 FROM 子句派生的表的相关性,例如,侧向相关性:
>>> from sqlalchemy import table, column, select, true >>> people = table("people", column("people_id"), column("age"), column("name")) >>> books = table("books", column("book_id"), column("owner_id")) >>> subq = ( ... select([books.c.book_id]) ... .where(books.c.owner_id == people.c.people_id) ... .lateral("book_subq") ... ) >>> print(select([people]).select_from(people.join(subq, true()))) SELECT people.people_id, people.age, people.name FROM people JOIN LATERAL (SELECT books.book_id AS book_id FROM books WHERE books.owner_id = people.people_id) AS book_subq ON true
另请参见
LATERAL correlation
Lateral
Select.lateral()
支持 TABLESAMPLE
SQL 标准的 TABLESAMPLE 可以使用FromClause.tablesample()方法呈现,该方法返回一个类似于别名的TableSample构造:
from sqlalchemy import func selectable = people.tablesample(func.bernoulli(1), name="alias", seed=func.random()) stmt = select([selectable.c.people_id])
假设people有一个列people_id,上述语句会被渲染为:
SELECT alias.people_id FROM people AS alias TABLESAMPLE bernoulli(:bernoulli_1) REPEATABLE (random())
对于复合主键列,.autoincrement指令不再隐式启用
SQLAlchemy 一直以来都有一个方便的特性,即为单列整数主键启用后端数据库的“自增”功能;所谓的“自增”,是指数据库列将包含任何 DDL 指令,以指示自增长整数标识符,例如在 PostgreSQL 上的 SERIAL 关键字或在 MySQL 上的 AUTO_INCREMENT,并且此外,方言将通过执行Table.insert()构造使用适合于该后端的技术来接收这些生成的值。
改变的是,此功能不再自动应用于复合主键;以前,表定义如下:
Table( "some_table", metadata, Column("x", Integer, primary_key=True), Column("y", Integer, primary_key=True), )
仅因为它在主键列列表中排在第一位,因此会应用于'x'列的“自增”语义。为了禁用此功能,必须关闭所有列上的autoincrement:
# old way Table( "some_table", metadata, Column("x", Integer, primary_key=True, autoincrement=False), Column("y", Integer, primary_key=True, autoincrement=False), )
使用新的行为,除非列被明确地标记为autoincrement=True,否则复合主键将不具有自增语义:
# column 'y' will be SERIAL/AUTO_INCREMENT/ auto-generating Table( "some_table", metadata, Column("x", Integer, primary_key=True), Column("y", Integer, primary_key=True, autoincrement=True), )
为了预测一些潜在的不向后兼容的情况,Table.insert()构造将对没有设置自增的复合主键列上的缺失主键值执行更彻底的检查;给定一个表,如下所示:
Table( "b", metadata, Column("x", Integer, primary_key=True), Column("y", Integer, primary_key=True), )
对于这个表没有提供任何值的 INSERT 将会产生警告:
SAWarning: Column 'b.x' is marked as a member of the primary key for table 'b', but has no Python-side or server-side default generator indicated, nor does it indicate 'autoincrement=True', and no explicit value is passed. Primary key columns may not store NULL. Note that as of SQLAlchemy 1.1, 'autoincrement=True' must be indicated explicitly for composite (e.g. multicolumn) primary keys if AUTO_INCREMENT/SERIAL/IDENTITY behavior is expected for one of the columns in the primary key. CREATE TABLE statements are impacted by this change as well on most backends.
对于从服务器端默认值或者触发器等不太常见的情况下接收主键值的列,可以使用FetchedValue来指示存在值生成器:
Table( "b", metadata, Column("x", Integer, primary_key=True, server_default=FetchedValue()), Column("y", Integer, primary_key=True, server_default=FetchedValue()), )
对于极少情况下实际上意图在其列中存储 NULL 的复合主键(仅在 SQLite 和 MySQL 上支持),请使用 nullable=True 指定列:
Table( "b", metadata, Column("x", Integer, primary_key=True), Column("y", Integer, primary_key=True, nullable=True), )
在相关更改中,可以在具有客户端或服务器端默认值的列上将 autoincrement 标志设置为 True。这通常不会对插入时列的行为产生太大影响。
另请参阅
不再为具有 AUTO_INCREMENT 的复合主键生成隐式 KEY
支持 IS DISTINCT FROM 和 IS NOT DISTINCT FROM
新运算符 ColumnOperators.is_distinct_from() 和 ColumnOperators.isnot_distinct_from() 允许 IS DISTINCT FROM 和 IS NOT DISTINCT FROM sql 操作:
>>> print(column("x").is_distinct_from(None)) x IS DISTINCT FROM NULL
处理 NULL、True 和 False:
>>> print(column("x").isnot_distinct_from(False)) x IS NOT DISTINCT FROM false
对于不具有此运算符的 SQLite,将渲染“IS” / “IS NOT”,在 SQLite 上与其他后端不同,对 NULL 有效:
>>> from sqlalchemy.dialects import sqlite >>> print(column("x").is_distinct_from(None).compile(dialect=sqlite.dialect())) x IS NOT NULL
完全外连接的核心和 ORM 支持
新标志 FromClause.outerjoin.full,在核心和 ORM 级别可用,指示编译器在通常渲染 LEFT OUTER JOIN 的地方渲染 FULL OUTER JOIN:
stmt = select([t1]).select_from(t1.outerjoin(t2, full=True))
该标志也适用于 ORM 级别:
q = session.query(MyClass).outerjoin(MyOtherClass, full=True)
ResultSet 列匹配增强;文本 SQL 的位置列设置
在 1.0 系列中对 ResultProxy 系统进行了一系列改进,作为 #918 的一部分,重新组织了内部结构,以便通过位置而不是通过匹配名称将游标绑定的结果列与表/ORM 元数据进行匹配,用于包含有关要返回的结果行的完整信息的编译 SQL 构造。这样可以大大节省 Python 的开销,同时在将 ORM 和 Core SQL 表达式与结果行链接时提供更高的准确性。在 1.1 中,这种重新组织在内部进一步进行了,并且还通过最近添加的 TextClause.columns() 方法可用于纯文本 SQL 构造。
TextAsFrom.columns() 现在按位置工作
TextClause.columns()方法是在 0.9 版中添加的,接受基于列的参数位置;在 1.1 版中,当所有列被位置传递时,这些列与最终结果集的关联也将按位置执行。这里的关键优势在于,现在可以将文本 SQL 链接到 ORM 级别的结果集,而无需处理模糊或重复的列名,也无需将标签方案与 ORM 级别的标签方案进行匹配。现在所需要的只是在文本 SQL 中的列的顺序与传递给TextClause.columns()的列参数相同:
from sqlalchemy import text stmt = text( "SELECT users.id, addresses.id, users.id, " "users.name, addresses.email_address AS email " "FROM users JOIN addresses ON users.id=addresses.user_id " "WHERE users.id = 1" ).columns(User.id, Address.id, Address.user_id, User.name, Address.email_address) query = session.query(User).from_statement(stmt).options(contains_eager(User.addresses)) result = query.all()
在上述文本 SQL 中,“id”列出现了三次,这通常会产生歧义。使用新功能,我们可以直接应用来自User和Address类的映射列,甚至可以将文本 SQL 中的Address.user_id列链接到users.id列,以供娱乐之用,而Query对象将接收到正确的可定位行,包括用于及早加载。
此更改与通过不同顺序将列传递给该方法的代码不兼容。希望由于这种方法一直以来都是以与文本 SQL 语句相同的顺序传递列而被记录的,这种影响将会很小,尽管内部并未检查此顺序。无论如何,该方法仅在 0.9 版中才被添加,并且可能尚未广泛使用。有关如何处理使用该方法的应用程序的行为更改的详细说明,请参阅 TextClause.columns() will match columns positionally, not by name, when passed positionally。
另请参阅
使用文本列表达式进行选择
TextClause.columns() will match columns positionally, not by name, when passed positionally - 向后兼容性说明
对于 Core/ORM SQL 构造,基于位置的匹配比基于名称的匹配更受信任
此更改的另一个方面是,匹配列的规则也已经修改,以更充分地依赖于编译后 SQL 结构中的“位置”匹配。考虑到以下语句:
ua = users.alias("ua") stmt = select([users.c.user_id, ua.c.user_id])
上述语句将编译为:
SELECT users.user_id, ua.user_id FROM users, users AS ua
在 1.0 版本中,上述语句在执行时将通过位置匹配与其原始编译结构相匹配,但由于语句中包含重复的 'user_id' 标签,因此“模糊列”规则仍然会介入并阻止从行中获取列。从 1.1 版开始,“模糊列”规则不会影响从列构造到 SQL 列的精确匹配,这是 ORM 用于获取列的方法:
result = conn.execute(stmt) row = result.first() # these both match positionally, so no error user_id = row[users.c.user_id] ua_id = row[ua.c.user_id] # this still raises, however user_id = row["user_id"]
几乎不太可能出现“模糊列”错误消息
作为这一更改的一部分,错误消息 Ambiguous column name '' in result set! try 'use_labels' option on select statement. 的措辞已经有所减少;由于使用 ORM 或 Core 编译的 SQL 结构时,此消息现在应该极为罕见,因此它仅在检索使用实际上具有歧义的字符串名称的结果列时才会说明 Ambiguous column name '' in result set column descriptions。它还现在引用了来自渲染的 SQL 语句本身的实际模糊名称,而不是指示用于获取的构造体的键或名称。
TextAsFrom.columns() 现在按位置工作
TextClause.columns() 方法在 0.9 版中添加了,它接受基于列的参数按位置排列;在 1.1 版中,当所有列按位置传递时,这些列与最终结果集的相关性也按位置执行。这里的关键优势是,现在可以将文本 SQL 链接到 ORM 级别的结果集,而无需处理模糊或重复的列名,也无需将标签方案与 ORM 级别的标签方案进行匹配。现在所需的是文本 SQL 中的列顺序与传递给 TextClause.columns() 的列参数的相同顺序:
from sqlalchemy import text stmt = text( "SELECT users.id, addresses.id, users.id, " "users.name, addresses.email_address AS email " "FROM users JOIN addresses ON users.id=addresses.user_id " "WHERE users.id = 1" ).columns(User.id, Address.id, Address.user_id, User.name, Address.email_address) query = session.query(User).from_statement(stmt).options(contains_eager(User.addresses)) result = query.all()
在上述文本 SQL 中,“id” 列出现三次,这通常会是模糊的。使用新功能,我们可以直接应用来自 User 和 Address 类的映射列,甚至可以将 Address.user_id 列链接到文本 SQL 中的 users.id 列以供娱乐,并且 Query 对象将接收到正确可用的行,包括用于急加载。
这一变化与将列以与文本语句中的顺序不同的顺序传递给方法的代码不兼容。希望由于这个方法一直以来都是以与文本 SQL 语句相同的顺序传递列而被记录的,因此这种影响将会很小,即使内部没有检查这一点。无论如何,该方法仅在 0.9 版本中添加,并且可能尚未广泛使用。有关如何处理使用它的应用程序的这种行为变化的详细说明,请参阅 TextClause.columns() will match columns positionally, not by name, when passed positionally。
另请参阅
使用文本列表达式进行选择
当按位置传递时,TextClause.columns() 将按位置而不是按名称匹配列 - 向后兼容性说明
对于核心/ORM SQL 构造,位置匹配比基于名称的匹配更可靠
这一变化的另一个方面是,匹配列的规则也已经修改,更充分地依赖“位置”匹配来编译 SQL 构造。给定如下语句:
ua = users.alias("ua") stmt = select([users.c.user_id, ua.c.user_id])
上述语句将编译为:
SELECT users.user_id, ua.user_id FROM users, users AS ua
在 1.0 版本中,上述语句在执行时将使用位置匹配与其原始编译构造相匹配,但是因为该语句包含了重复的 'user_id' 标签,所以“模糊列”规则仍然会介入并阻止从行中获取列。从 1.1 版本开始,“模糊列”规则不会影响从列构造到 SQL 列的精确匹配,这是 ORM 用于获取列的方法:
result = conn.execute(stmt) row = result.first() # these both match positionally, so no error user_id = row[users.c.user_id] ua_id = row[ua.c.user_id] # this still raises, however user_id = row["user_id"]
很少出现“模糊列”错误消息
作为此更改的一部分,错误消息 Ambiguous column name '' in result set! try 'use_labels' option on select statement. 的措辞已经减弱;因为使用 ORM 或核心编译 SQL 构造时,此消息现在应该极为罕见,它只是简单地说明了 Ambiguous column name '' in result set column descriptions,仅当通过实际模糊的字符串名称检索结果列时,例如在上面的示例中使用 row['user_id']。它现在还引用了来自渲染的 SQL 语句本身的实际模糊名称,而不是指示用于获取的构造的键或名称。
SqlAlchemy 2.0 中文文档(七十五)(4)https://developer.aliyun.com/article/1562372