SqlAlchemy 2.0 中文文档(七十五)(3)https://developer.aliyun.com/article/1562371
支持 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
元素集合现在是一个列表而不是元组。这是因为列表适用于元素的长度可变的同质项序列,其中元素的位置在语义上不重要。
Enum.enums
集合现在是一个列表而不是元组
作为对Enum
的更改的一部分,Enum.enums
元素集合现在是一个列表而不是元组。这是因为列表适用于元素的长度可变的同质项序列,其中元素的位置在语义上不重要。
Core 结果行容纳负整数索引
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 本机枚举对象,以及诸如在数组中使用非本地 ENUM 类型且 CHECK 约束不可行等边缘情况,当使用Enum.validate_strings
标志时,Enum
数据类型现在对输入值进行 Python 内部验证(1.1.0b2):
>>> 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 约束的创建。
所有情况下将非本地布尔整数值强制转换为零/一/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。
在日志和异常显示中现在截断大的��数和行值
作为 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',)
核心添加了 JSON 支持
由于 MySQL 现在除了 PostgreSQL JSON 数据类型外还有 JSON 数据类型,核心现在获得了一个sqlalchemy.types.JSON
数据类型,这是这两种数据类型的基础。使用这种类型可以在 PostgreSQL 和 MySQL 之间以一种不可知的方式访问“getitem”运算符和“getpath”运算符。
新数据类型还对 NULL 值的处理以及表达式处理进行了一系列改进。
另请参阅
MySQL JSON 支持
JSON
JSON
JSON
与 ORM 操作一起插入 JSON“null”时,如预期那样插入,当不存在时则被省略
JSON
类型及其后代类型JSON
和JSON
有一个标志JSON.none_as_null
,当设置为 True 时,表示 Python 值None
应该转换为 SQL NULL 而不是 JSON NULL 值。此标志默认为 False,这意味着 Python 值None
应该导致 JSON NULL 值。
在以下情况下,此逻辑将失败,并已得到纠正:
1. 当列还包含默认值或服务器默认值时,对于期望持久化 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. 当列没有包含默认值或服务器默认值时,配置为 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 列如果未提供任何值且未建立默认值,则不会插入 JSON NULL。
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 #### 在 ORM 操作中插入 JSON “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 列将不插入 JSON NULL。
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
类型及其派生类型。
新增 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
类型及其派生类型。
Core 添加了数组支持;新增 ANY 和 ALL 运算符
除了在 Correct SQL Types are Established from Indexed Access of ARRAY, JSON, HSTORE 中描述的 PostgreSQL ARRAY
类型的增强功能外,ARRAY
的基类本身已经移动到核心中的一个新类 ARRAY
中。
数组是 SQL 标准的一部分,还有一些面向数组的函数,如 array_agg()
和 unnest()
。为了支持这些构造,不仅仅是针对 PostgreSQL,还有可能是将来其他支持数组的后端,如 DB2,现在大部分 SQL 表达式的数组逻辑都在核心中。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))
新功能特性���“WITHIN GROUP”,array_agg 和 set 聚合函数
使用新的 ARRAY
类型,我们还可以实现一个预先类型化的函数,用于返回一个数组的 array_agg()
SQL 函数,现在可以使用 array_agg
进行调用:
from sqlalchemy import func stmt = select([func.array_agg(table.c.value)])
还添加了一个 PostgreSQL 元素,用于聚合 ORDER BY,通过aggregate_order_by
:
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
。
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")
用于表对象的多租户模式翻译
为了支持一个应用程序使用许多模式中相同的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)
另请参阅
模式名称的翻译
“友好”地将 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()
函数现在调用一个完全独立的方言/编译器,仅用于普通字符串打印而没有设置特定方言,因此随着更多“只是显示给我一个字符串!”的情况出现,这些可以添加到此方言/编译器中,而不会影响真实方言上的行为。
另请参阅
查询的字符串化将询问会话以获取正确的方言
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
的 lazyload 情况依赖于在加载时用绑定参数替换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()
函数按照意图移除。
关键行为变化 - ORM
如果没有提供值且没有建立默认值,JSON 列将不会插入 JSON NULL
如 JSON “null” is inserted as expected with ORM operations, omitted when not present 中详细说明,如果值完全缺失,JSON
将不会呈现 JSON“null”值。为了防止 SQL NULL,应该设置一个默认值。给定以下映射:
class MyObject(Base): # ... json_value = Column(JSON(none_as_null=False), nullable=False)
以下刷新操作将由于完整性错误而失败:
obj = MyObject() # note no json_value session.add(obj) session.commit() # will fail with integrity error
如果列的默认值应为 JSON NULL,请在列上设置此值:
class MyObject(Base): # ... json_value = Column(JSON(none_as_null=False), nullable=False, default=JSON.NULL)
或者,确保对象上存在该值:
obj = MyObject(json_value=None) session.add(obj) session.commit() # will insert JSON NULL
请注意,为默认值设置None
与完全省略它相同;JSON.none_as_null
标志不影响传递给Column.default
或Column.server_default
的None
值:
# default=None is the same as omitting it entirely, does not apply JSON NULL json_value = Column(JSON(none_as_null=False), nullable=False, default=None)
另请参见
JSON “null” is inserted as expected with ORM operations, omitted when not present ### 列不再通过 DISTINCT + ORDER BY 冗余添加
现在,类似以下的查询将仅增加那些在 SELECT 列表中缺失的列,而不会重复:
q = ( session.query(User.id, User.name.label("name")) .distinct() .order_by(User.id, User.name, User.fullname) )
产生:
SELECT DISTINCT user.id AS a_id, user.name AS name, user.fullname AS a_fullname FROM a ORDER BY user.id, user.name, user.fullname
以前,它会产生:
SELECT DISTINCT user.id AS a_id, user.name AS name, user.name AS a_name, user.fullname AS a_fullname FROM a ORDER BY user.id, user.name, user.fullname
在上面的例子中,user.name
列被不必要地添加。结果不会受影响,因为额外的列在任何情况下都不包含在结果中,但这些列是不必要的。
此外,当通过向 Query.distinct()
传递表达式来使用 PostgreSQL DISTINCT ON 格式时,上述“添加列”逻辑将被完全禁用。
当查询被捆绑到子查询中以实现连接的急加载时,"增强列列表"规则必须更加积极,以便仍然可以满足 ORDER BY,因此此情况保持不变。
#3641 ### 相同名称的 @validates 装饰器现在将引发异常
validates()
装饰器只打算为特定属性名称的每个类创建一次。创建多个现在会引发错误,而以前会悄悄地选择最后定义的验证器:
class A(Base): __tablename__ = "a" id = Column(Integer, primary_key=True) data = Column(String) @validates("data") def _validate_data_one(self): assert "x" in data @validates("data") def _validate_data_two(self): assert "y" in data configure_mappers()
将引发:
sqlalchemy.exc.InvalidRequestError: A validation function for mapped attribute 'data' on mapper Mapper|A|a already exists.
#3776 ### 如果未提供值且未建立默认值,则 JSON 列将不插入 JSON NULL
如 JSON “null” 在 ORM 操作中如预期地插入,当不存在时被省略 中详细说明的,JSON
如果完全缺少值,则不会呈现 JSON “null” 值。为了防止 SQL NULL,应设置默认值。给定以下映射:
class MyObject(Base): # ... json_value = Column(JSON(none_as_null=False), nullable=False)
以下刷新操作将因完整性错误而失败:
obj = MyObject() # note no json_value session.add(obj) session.commit() # will fail with integrity error
如果列的默认值应为 JSON NULL,请在 Column 上设置此值:
class MyObject(Base): # ... json_value = Column(JSON(none_as_null=False), nullable=False, default=JSON.NULL)
或者,确保对象上存在该值:
obj = MyObject(json_value=None) session.add(obj) session.commit() # will insert JSON NULL
请注意,为默认设置 None
与完全省略它相同;JSON.none_as_null
标志不影响传递给 Column.default
或 Column.server_default
的 None
的值:
# default=None is the same as omitting it entirely, does not apply JSON NULL json_value = Column(JSON(none_as_null=False), nullable=False, default=None)
另请参阅
JSON “null” 在 ORM 操作中如预期地插入,当不存在时被省略
使用 DISTINCT + ORDER BY 不再冗余添加列
以下查询现在只会增补那些在 SELECT 列表中缺失的列,而不会重复:
q = ( session.query(User.id, User.name.label("name")) .distinct() .order_by(User.id, User.name, User.fullname) )
产生:
SELECT DISTINCT user.id AS a_id, user.name AS name, user.fullname AS a_fullname FROM a ORDER BY user.id, user.name, user.fullname
以前,它会产生:
SELECT DISTINCT user.id AS a_id, user.name AS name, user.name AS a_name, user.fullname AS a_fullname FROM a ORDER BY user.id, user.name, user.fullname
在上面的例子中,user.name
列被不必要地添加。结果不会受影响,因为额外的列在任何情况下都不包含在结果中,但这些列是不必要的。
此外,当通过将表达式传递给Query.distinct()
来使用 PostgreSQL DISTINCT ON 格式时,上述“添加列”逻辑将完全禁用。
当查询被捆绑到子查询中以进行连接式贪婪加载时,“增补列列表”规则必须更加积极,以便仍然可以满足 ORDER BY,因此这种情况保持不变。
相同名称的@validates 装饰器现在将引发异常
validates()
装饰器只打算为特定属性名称的类创建一次。现在创建多个会引发错误,而以前它会悄悄地选择最后定义的验证器:
class A(Base): __tablename__ = "a" id = Column(Integer, primary_key=True) data = Column(String) @validates("data") def _validate_data_one(self): assert "x" in data @validates("data") def _validate_data_two(self): assert "y" in data configure_mappers()
将引发:
sqlalchemy.exc.InvalidRequestError: A validation function for mapped attribute 'data' on mapper Mapper|A|a already exists.
核心的关键行为变化
TextClause.columns()将按位置匹配列,而不是按名称匹配
TextClause.columns()
方法的新行为,它本身是在 0.9 系列中最近添加的,是当列按位置传递而没有任何额外的关键字参数时,它们与最终结果集的列按位置链接,而不再按名称。希望这种变化的影响会很小,因为该方法一直以来都有文档说明传递的列与文本 SQL 语句的顺序相同,这似乎是直观的,即使内部没有检查这一点。
通过将Column
对象按位置传递给该方法的应用程序必须确保这些Column
对象的位置与这些列在文本 SQL 中声明的位置相匹配。
例如,像下面这样的代码:
stmt = text("SELECT id, name, description FROM table") # no longer matches by name stmt = stmt.columns(my_table.c.name, my_table.c.description, my_table.c.id)
现在不再按预期工作;给定列的顺序现在很重要:
# correct version stmt = stmt.columns(my_table.c.id, my_table.c.name, my_table.c.description)
可能更有可能的是,像这样工作的语句:
stmt = text("SELECT * FROM table") stmt = stmt.columns(my_table.c.id, my_table.c.name, my_table.c.description)
现在略有风险,因为“*”规范通常会按照表本身中的顺序提供列。如果表的结构因模式更改而更改,则此顺序可能不再相同。因此,在使用TextClause.columns()
时,建议在文本 SQL 中明确列出所需的列,尽管在文本 SQL 中不再需要担心列名本身。
另请参阅
ResultSet column matching enhancements; positional column setup for textual SQL ### 字符串 server_default 现在以文本引号引用
作为纯 Python 字符串传递给 Column.server_default
的服务器默认值现在通过文本引用系统传递:
>>> from sqlalchemy.schema import MetaData, Table, Column, CreateTable >>> from sqlalchemy.types import String >>> t = Table("t", MetaData(), Column("x", String(), server_default="hi ' there")) >>> print(CreateTable(t)) CREATE TABLE t ( x VARCHAR DEFAULT 'hi '' there' )
以前的引用会直接渲染。对于存在此类用例并且正在解决此问题的应用程序,此更改可能不兼容。
#3809 ### 具有 LIMIT/OFFSET/ORDER BY 的 SELECT 的 UNION 或类似结构现在会对嵌入的 SELECT 进行括号化
一个问题,就像其他问题一样,长期以来受 SQLite 缺乏功能的驱动,现在已经增强以在所有支持的后端上工作。我们指的是一个查询,它是 SELECT 语句的 UNION,这些语句本身包含了包含 LIMIT、OFFSET 和/或 ORDER BY 的行限制或排序功能:
(SELECT x FROM table1 ORDER BY y LIMIT 1) UNION (SELECT x FROM table2 ORDER BY y LIMIT 2)
上述查询需要在每个子查询中加上括号,以便正确地对子结果进行分组。在 SQLAlchemy 核心中生成上述语句的形式如下:
stmt1 = select([table1.c.x]).order_by(table1.c.y).limit(1) stmt2 = select([table1.c.x]).order_by(table2.c.y).limit(2) stmt = union(stmt1, stmt2)
以前,上述结构不会为内部 SELECT 语句产生括号化,从而产生一个在所有后端上都失败的查询。
上述格式在 SQLite 上仍然会失败;此外,包含 ORDER BY 但不包含 LIMIT/SELECT 的格式在 Oracle 上仍然会失败。这不是一个向后不兼容的更改,因为查询如果没有括号也会失败;有了修复,查询至少在所有其他数据库上可以工作。
在所有情况下,为了产生一个在 SQLite 上和在所有情况下在 Oracle 上都有效的有限 SELECT 语句的 UNION,子查询必须是一个 ALIAS 的 SELECT:
stmt1 = select([table1.c.x]).order_by(table1.c.y).limit(1).alias().select() stmt2 = select([table2.c.x]).order_by(table2.c.y).limit(2).alias().select() stmt = union(stmt1, stmt2)
这种解决方法适用于所有 SQLAlchemy 版本。在 ORM 中,它的形式如下:
stmt1 = session.query(Model1).order_by(Model1.y).limit(1).subquery().select() stmt2 = session.query(Model2).order_by(Model2.y).limit(1).subquery().select() stmt = session.query(Model1).from_statement(stmt1.union(stmt2))
这里的行为与 SQLAlchemy 0.9 中引入的“join 重写”行为有许多相似之处许多 JOIN 和 LEFT OUTER JOIN 表达式将不再被包装在 (SELECT * FROM …) AS ANON_1 中;然而,在这种情况下,我们选择不添加新的重写行为以适应 SQLite 的情况。现有的重写行为已经非常复杂了,而具有带括号的 SELECT 语句的 UNION 的情况比该功能的“右嵌套连接”用例要少得多。
#2528 ### TextClause.columns()
将按位置匹配列,而不是按名称匹配
TextClause.columns()
方法的新行为,它本身是最近添加的 0.9 系列的一部分,是当列被以位置传递且没有任何额外的关键字参数时,它们链接到最终结果集的列的位置,而不再是按名称。希望由于该方法始终被记录为说明列按照文本 SQL 语句的相同顺序传递,这个更改的影响会很小,尽管内部并没有检查这一点。
使用这种方法的应用程序通过按位置传递 Column
对象来确保这些 Column
对象的位置与文本 SQL 中这些列的位置相匹配。
例如,像下面的代码:
stmt = text("SELECT id, name, description FROM table") # no longer matches by name stmt = stmt.columns(my_table.c.name, my_table.c.description, my_table.c.id)
将不再按预期工作;给出的列的顺序现在很重要:
# correct version stmt = stmt.columns(my_table.c.id, my_table.c.name, my_table.c.description)
更有可能的是,像这样工作的语句:
stmt = text("SELECT * FROM table") stmt = stmt.columns(my_table.c.id, my_table.c.name, my_table.c.description)
现在稍微有风险,因为“*”规范通常会按照它们在表本身中出现的顺序传送列。如果表的结构因模式更改而更改,则此顺序可能不再相同。因此,在使用 TextClause.columns()
时,建议在文本 SQL 中明确列出所需的列,尽管在文本 SQL 中不再需要担心列名本身。
另见
ResultSet 列匹配增强;文本 SQL 的位置列设置
字符串 server_default 现在是字面引用
传递给 Column.server_default
的服务器默认值,作为一个带有引号的普通 Python 字符串,现在通过字面引用系统传递:
>>> from sqlalchemy.schema import MetaData, Table, Column, CreateTable >>> from sqlalchemy.types import String >>> t = Table("t", MetaData(), Column("x", String(), server_default="hi ' there")) >>> print(CreateTable(t)) CREATE TABLE t ( x VARCHAR DEFAULT 'hi '' there' )
以前引号会直接呈现。此更改对于具有这种用例并围绕此问题进行工作的应用程序可能不兼容。
UNION 或类似 SELECT 的带有 LIMIT/OFFSET/ORDER BY 的现在括号化嵌入式 SELECTs
像其他问题一样,长期受 SQLite 能力不足的驱动,现在已经增强以在所有支持的后端上工作。我们指的是一个查询,它是 SELECT 语句的 UNION,这些语句本身包含行限制或排序功能,包括 LIMIT、OFFSET 和/或 ORDER BY:
(SELECT x FROM table1 ORDER BY y LIMIT 1) UNION (SELECT x FROM table2 ORDER BY y LIMIT 2)
上述查询要求每个子选择中都要有括号,以便正确分组子结果。在 SQLAlchemy Core 中生成上述语句如下:
stmt1 = select([table1.c.x]).order_by(table1.c.y).limit(1) stmt2 = select([table1.c.x]).order_by(table2.c.y).limit(2) stmt = union(stmt1, stmt2)
以前,上述结构不会为内部 SELECT 语句产生括号,导致在所有后端上都失败的查询。
上述格式在 SQLite 上仍将失败;此外,包含 ORDER BY 但没有 LIMIT/SELECT 的格式在 Oracle 上仍将失败。这不是一个不兼容的更改,因为即使没有括号,查询也会失败;通过修复,查询至少在所有其他数据库上都能正常工作。
在所有情况下,为了生成一个在 SQLite 上和在所有情况下在 Oracle 上都能正常工作的有限 SELECT 语句的 UNION,子查询必须是一个 ALIAS 的 SELECT:
stmt1 = select([table1.c.x]).order_by(table1.c.y).limit(1).alias().select() stmt2 = select([table2.c.x]).order_by(table2.c.y).limit(2).alias().select() stmt = union(stmt1, stmt2)
这个解决方法适用于所有 SQLAlchemy 版本。在 ORM 中,它看起来像:
stmt1 = session.query(Model1).order_by(Model1.y).limit(1).subquery().select() stmt2 = session.query(Model2).order_by(Model2.y).limit(1).subquery().select() stmt = session.query(Model1).from_statement(stmt1.union(stmt2))
这里的行为与 SQLAlchemy 0.9 中引入的“连接重写”行为有许多相似之处,许多 JOIN 和 LEFT OUTER JOIN 表达式将不再被包装在 (SELECT * FROM …) AS ANON_1 中;然而,在这种情况下,我们选择不添加新的重写行为来适应 SQLite 的情况。现有的重写行为已经非常复杂,而带有括号的 SELECT 语句的 UNION 情况比该功能的“右嵌套连接”用例要少得多。
SqlAlchemy 2.0 中文文档(七十五)(5)https://developer.aliyun.com/article/1562373