SqlAlchemy 2.0 中文文档(二十四)(1)https://developer.aliyun.com/article/1560544
使用 SQL 表达式与会话
SQL 表达式和字符串可以通过其事务上下文在 Session
中执行。这最容易通过 Session.execute()
方法来实现,该方法以与 Engine
或 Connection
相同的方式返回一个 CursorResult
:
Session = sessionmaker(bind=engine) session = Session() # execute a string statement result = session.execute(text("select * from table where id=:id"), {"id": 7}) # execute a SQL expression construct result = session.execute(select(mytable).where(mytable.c.id == 7))
Session
当前持有的 Connection
可以通过 Session.connection()
方法访问:
connection = session.connection()
上面的示例涉及到绑定到单个 Engine
或 Connection
的 Session
。要使用绑定到多个引擎或根本没有绑定到引擎的 Session
执行语句,Session.execute()
和 Session.connection()
都接受一个绑定参数字典 Session.execute.bind_arguments
,其中可能包括 “mapper”,该参数传递了一个映射类或 Mapper
实例,用于定位所需引擎的正确上下文:
Session = sessionmaker() session = Session() # need to specify mapper or class when executing result = session.execute( text("select * from table where id=:id"), {"id": 7}, bind_arguments={"mapper": MyMappedClass}, ) result = session.execute( select(mytable).where(mytable.c.id == 7), bind_arguments={"mapper": MyMappedClass} ) connection = session.connection(MyMappedClass)
从版本 1.4 开始变更:Session.execute()
的 mapper
和 clause
参数现在作为字典的一部分发送,作为 Session.execute.bind_arguments
参数。以前的参数仍然被接受,但此用法已被弃用。
强制将 NULL 值放入具有默认值的列
ORM 认为对象上从未设置的任何属性都是“默认”情况;该属性将从 INSERT 语句中省略:
class MyObject(Base): __tablename__ = "my_table" id = mapped_column(Integer, primary_key=True) data = mapped_column(String(50), nullable=True) obj = MyObject(id=1) session.add(obj) session.commit() # INSERT with the 'data' column omitted; the database # itself will persist this as the NULL value
如果在 INSERT 中省略了某列,则该列将被设置为 NULL 值,除非该列设置了默认值,在这种情况下,默认值将被保留。这适用于纯 SQL 视角下具有服务器端默认值的情况,也适用于 SQLAlchemy 的插入行为,无论是客户端默认值还是服务器端默认值:
class MyObject(Base): __tablename__ = "my_table" id = mapped_column(Integer, primary_key=True) data = mapped_column(String(50), nullable=True, server_default="default") obj = MyObject(id=1) session.add(obj) session.commit() # INSERT with the 'data' column omitted; the database # itself will persist this as the value 'default'
然而,在 ORM 中,即使将 Python 值 None
显式地分配给对象,这也被视为相同,就像从未分配过值一样:
class MyObject(Base): __tablename__ = "my_table" id = mapped_column(Integer, primary_key=True) data = mapped_column(String(50), nullable=True, server_default="default") obj = MyObject(id=1, data=None) session.add(obj) session.commit() # INSERT with the 'data' column explicitly set to None; # the ORM still omits it from the statement and the # database will still persist this as the value 'default'
上述操作将持久化到 data
列的服务器默认值为 "default"
,而不是 SQL NULL,即使传递了 None
;这是 ORM 的长期行为,许多应用程序都将其视为假设。
那么,如果我们想要在这列中实际放入 NULL 值,即使该列有默认值呢?有两种方法。一种是在每个实例级别上,我们使用 null
SQL 构造分配属性:
from sqlalchemy import null obj = MyObject(id=1, data=null()) session.add(obj) session.commit() # INSERT with the 'data' column explicitly set as null(); # the ORM uses this directly, bypassing all client- # and server-side defaults, and the database will # persist this as the NULL value
null
SQL 结构总是将 SQL NULL 值直接包含在目标 INSERT 语句中。
如果我们希望能够使用 Python 值 None
并且将其作为 NULL 持久化,尽管存在列默认值,我们可以在 ORM 中使用 Core 级别的修饰符 TypeEngine.evaluates_none()
进行配置,该修饰符指示 ORM 应该将值 None
与任何其他值一样对待并将其传递,而不是将其省略为“丢失”的值:
class MyObject(Base): __tablename__ = "my_table" id = mapped_column(Integer, primary_key=True) data = mapped_column( String(50).evaluates_none(), # indicate that None should always be passed nullable=True, server_default="default", ) obj = MyObject(id=1, data=None) session.add(obj) session.commit() # INSERT with the 'data' column explicitly set to None; # the ORM uses this directly, bypassing all client- # and server-side defaults, and the database will # persist this as the NULL value
获取服务器生成的默认值
正如在章节 Server-invoked DDL-Explicit Default Expressions 和 Marking Implicitly Generated Values, timestamps, and Triggered Columns 中介绍的,Core 支持数据库列的概念,其中数据库本身在 INSERT 时生成值,在不太常见的情况下,在 UPDATE 语句中生成值。ORM 功能支持这些列,以便能够在刷新时获取这些新生成的值。在服务器生成的主键列的情况下,由于 ORM 必须在对象持久化后知道其主键,因此需要这种行为。
在绝大多数情况下,由数据库自动生成值的主键列都是简单的整数列,这些列由数据库实现为所谓的“自增”列,或者是与列关联的序列。SQLAlchemy Core 中的每个数据库方言都支持一种检索这些主键值的方法,通常是原生于 Python DBAPI,并且通常这个过程是自动的。关于这一点,有更多的文档说明在 Column.autoincrement
中。
对于不是主键列或不是简单自增整数列的服务器生成列,ORM 要求这些列使用适当的 server_default
指令标记,以允许 ORM 检索此值。然而,并不是所有方法都受到所有后端的支持,因此必须注意使用适当的方法。要回答的两个问题是,1. 此列是否是主键列,2. 数据库是否支持 RETURNING 或等效操作,如 “OUTPUT inserted”;这些是 SQL 短语,它们在调用 INSERT 或 UPDATE 语句时同时返回服务器生成的值。RETURNING 目前由 PostgreSQL、Oracle、MariaDB 10.5、SQLite 3.35 和 SQL Server 支持。
情况 1:非主键,支持 RETURNING 或等效操作
在这种情况下,列应标记为FetchedValue
,或者用显式的Column.server_default
。ORM 在执行 INSERT 语句时将自动将这些列添加到 RETURNING 子句中,假设 Mapper.eager_defaults
参数设置为 True
,或者对于同时支持 RETURNING 和 insertmanyvalues 的方言,保持其默认设置为 "auto"
:
class MyModel(Base): __tablename__ = "my_table" id = mapped_column(Integer, primary_key=True) # server-side SQL date function generates a new timestamp timestamp = mapped_column(DateTime(), server_default=func.now()) # some other server-side function not named here, such as a trigger, # populates a value into this column during INSERT special_identifier = mapped_column(String(50), server_default=FetchedValue()) # set eager defaults to True. This is usually optional, as if the # backend supports RETURNING + insertmanyvalues, eager defaults # will take place regardless on INSERT __mapper_args__ = {"eager_defaults": True}
在上面的示例中,如果客户端未为“timestamp”或“special_identifier”指定显式值,则 INSERT 语句将在 RETURNING 子句中包含“timestamp”和“special_identifier”列,以便立即使用。在 PostgreSQL 数据库中,上述表的 INSERT 如下所示:
INSERT INTO my_table DEFAULT VALUES RETURNING my_table.id, my_table.timestamp, my_table.special_identifier
从版本 2.0.0rc1 起更改:Mapper.eager_defaults
参数现在默认为新设置 "auto"
,如果后端数据库同时支持 RETURNING 和 insertmanyvalues,将自动使用 RETURNING 获取 INSERT 上生成的默认值。
注意
Mapper.eager_defaults
的 "auto"
值仅适用于 INSERT 语句。即使可用,UPDATE 语句也不会使用 RETURNING,除非 Mapper.eager_defaults
设置为 True
。这是因为 UPDATE 没有等效的“insertmanyvalues”特性,因此 UPDATE RETURNING 将要求对每个要更新的行分别发出 UPDATE 语句。
情况 2:表包含不兼容于 RETURNING 的触发器生成的值
Mapper.eager_defaults
的 "auto"
设置意味着支持 RETURNING 的后端通常会在 INSERT 语句中使用 RETURNING 以检索新生成的默认值。但是,使用触发器生成的服务器值存在限制,使得无法使用 RETURNING:
- SQL Server 不允许在 INSERT 语句中使用 RETURNING 来检索触发器生成的值;该语句将失败。
- SQLite 在将 RETURNING 与触发器组合使用时存在限制,因此 RETURNING 子句将不会包含插入的值
- 其他后端可能在与触发器一起使用 RETURNING,或者其他类型的服务器生成值时存在限制。
要禁用对这些值的 RETURNING 使用,不仅包括服务器生成的默认值,还要确保 ORM 永远不会与特定表使用 RETURNING,请为映射的 Table
指定 Table.implicit_returning
为 False
。使用声明性映射如下所示:
class MyModel(Base): __tablename__ = "my_table" id: Mapped[int] = mapped_column(primary_key=True) data: Mapped[str] = mapped_column(String(50)) # assume a database trigger populates a value into this column # during INSERT special_identifier = mapped_column(String(50), server_default=FetchedValue()) # disable all use of RETURNING for the table __table_args__ = {"implicit_returning": False}
在使用 pyodbc 驱动程序的 SQL Server 上,对上述表的 INSERT 不会使用 RETURNING,并将使用 SQL Server 的 scope_identity()
函数来检索新生成的主键值:
INSERT INTO my_table (data) VALUES (?); select scope_identity()
另请参阅
INSERT 行为 - 关于 SQL Server 方言获取新生成主键值的方法的背景信息
情况 3:非主键,不支持或不需要 RETURNING 或等效功能
该情况与上述情况 1 相同,但通常我们不希望使用 Mapper.eager_defaults
,因为在没有 RETURNING 支持的情况下,其当前实现是为每行发出一个 SELECT,这不是高效的。因此,在下面的映射中省略了该参数:
class MyModel(Base): __tablename__ = "my_table" id = mapped_column(Integer, primary_key=True) timestamp = mapped_column(DateTime(), server_default=func.now()) # assume a database trigger populates a value into this column # during INSERT special_identifier = mapped_column(String(50), server_default=FetchedValue())
在不包含 RETURNING 或“insertmanyvalues”支持的后端上插入具有上述映射的记录后,“timestamp” 和 “special_identifier” 列将保持为空,并且在刷新后首次访问时,例如标记为“过期”时,将通过第二个 SELECT 语句获取。
如果 Mapper.eager_defaults
明确提供了值 True
,并且后端数据库不支持 RETURNING 或等效功能,则 ORM 将在 INSERT 语句后立即发出 SELECT 语句以获取新生成的值;如果没有 RETURNING 可用,ORM 目前无法批量选择许多新插入的行。这通常是不希望的,因为它会向刷新过程添加额外的 SELECT 语句,这些语句可能是不必要的。在 MySQL(而不是 MariaDB)上使用上述映射与将 Mapper.eager_defaults
标志设置为 True 会导致刷新时生成以下 SQL:
INSERT INTO my_table () VALUES () -- when eager_defaults **is** used, but RETURNING is not supported SELECT my_table.timestamp AS my_table_timestamp, my_table.special_identifier AS my_table_special_identifier FROM my_table WHERE my_table.id = %s
未来的 SQLAlchemy 版本可能会在没有 RETURNING 的情况下,通过批量处理单个 SELECT 语句中的多行来提高急切默认值的效率。
情况 4:主键,支持 RETURNING 或等效功能
具有服务器生成值的主键列必须在 INSERT 后立即获取;ORM 只能访问具有主键值的行,因此如果主键由服务器生成,则 ORM 需要一种在 INSERT 后立即检索该新值的方法。
如上所述,对于整数“自动增量”列,以及标记有 Identity
和特殊构造(如 PostgreSQL SERIAL)的列,Core 会自动处理这些类型;数据库包括用于获取“最后插入 id”的函数,在不支持 RETURNING 的情况下,以及支持 RETURNING 的情况下 SQLAlchemy 将使用该函数。
例如,在 Oracle 中,如果将列标记为 Identity
,则自动使用 RETURNING 获取新的主键值:
class MyOracleModel(Base): __tablename__ = "my_table" id: Mapped[int] = mapped_column(Identity(), primary_key=True) data: Mapped[str] = mapped_column(String(50))
如上所述,上述模型在 Oracle 上的 INSERT 如下所示:
INSERT INTO my_table (data) VALUES (:data) RETURNING my_table.id INTO :ret_0
SQLAlchemy 为“data”字段渲染了一个 INSERT,但在 RETURNING 子句中仅包含了“id”,以便在服务器端生成“id”,并立即返回新值。
对于由服务器端函数或触发器生成的非整数值,以及来自表格本身之外的结构(包括显式序列和触发器)的整数值,必须在表格元数据中标记服务器默认生成。再次以 Oracle 为例,我们可以举例说明一个类似上述的表格,使用 Sequence
构造命名一个显式序列:
class MyOracleModel(Base): __tablename__ = "my_table" id: Mapped[int] = mapped_column(Sequence("my_oracle_seq"), primary_key=True) data: Mapped[str] = mapped_column(String(50))
在 Oracle 上,对于此模型的 INSERT 如下所示:
INSERT INTO my_table (id, data) VALUES (my_oracle_seq.nextval, :data) RETURNING my_table.id INTO :ret_0
在上述情况中,SQLAlchemy 为主键列渲染了 my_sequence.nextval
,以便用于新的主键生成,并且还使用 RETURNING 立即获取新值。
如果数据源不是由简单的 SQL 函数或 Sequence
表示,例如在使用触发器或生成新值的数据库特定数据类型时,可以通过在列定义中使用 FetchedValue
来指示值生成默认值的存在。下面是一个使用 SQL Server TIMESTAMP 列作为主键的模型;在 SQL Server 上,此数据类型会自动生成新值,因此在表格元数据中通过为 Column.server_default
参数指示 FetchedValue
来表示这一点:
class MySQLServerModel(Base): __tablename__ = "my_table" timestamp: Mapped[datetime.datetime] = mapped_column( TIMESTAMP(), server_default=FetchedValue(), primary_key=True ) data: Mapped[str] = mapped_column(String(50))
在 SQL Server 上,对于上述表格的 INSERT 如下所示:
INSERT INTO my_table (data) OUTPUT inserted.timestamp VALUES (?)
情况 5:不支持主键、RETURNING 或等效功能。
在此领域,我们正在为 MySQL 等数据库生成行,其中服务器上正在发生一些默认生成的手段,但这些手段不在数据库的通常自增例程中。在这种情况下,我们必须确保 SQLAlchemy 可以“预先执行”默认值,这意味着它必须是一个明确的 SQL 表达式。
注意
本节将说明涉及 MySQL 日期时间值的多个配方,因为该后端的日期时间数据类型具有额外的特殊要求,这些要求对于说明非常有用。但是请注意,除了通常的单列自增整数值之外,MySQL 需要为任何用作主键的自动生成数据类型显式的“预执行”默认生成器。
具有 DateTime 主键的 MySQL
以 MySQL 的DateTime
列为例,我们使用“NOW()”SQL 函数添加了一个明确的预执行支持的默认值:
class MyModel(Base): __tablename__ = "my_table" timestamp = mapped_column(DateTime(), default=func.now(), primary_key=True)
在上述情况下,我们选择“NOW()”函数以向列传递日期时间值。上述生成的 SQL 是:
SELECT now() AS anon_1 INSERT INTO my_table (timestamp) VALUES (%s) ('2018-08-09 13:08:46',)
具有 TIMESTAMP 主键的 MySQL
当使用 MySQL 的TIMESTAMP
数据类型时,MySQL 通常会自动将服务器端默认与该数据类型关联起来。但是,当我们将其用作主键时,Core 无法检索新生成的值,除非我们自己执行该函数。由于 MySQL 上的TIMESTAMP
实际上存储了一个二进制值,因此我们需要在“NOW()”的使用中添加一个额外的“CAST”,以便检索到可以持久化到列中的二进制值:
from sqlalchemy import cast, Binary class MyModel(Base): __tablename__ = "my_table" timestamp = mapped_column( TIMESTAMP(), default=cast(func.now(), Binary), primary_key=True )
以上,在选择“NOW()”函数的同时,我们还使用了Binary
数据类型结合cast()
,以便返回的值是二进制的。在 INSERT 中从上述渲染的 SQL 如下所示:
SELECT CAST(now() AS BINARY) AS anon_1 INSERT INTO my_table (timestamp) VALUES (%s) (b'2018-08-09 13:08:46',)
另见
列插入/更新默认值
注意事项:对于用于 INSERT 或 UPDATE 的急切提取客户端调用的 SQL 表达式
上述示例指示了使用Column.server_default
创建包含其 DDL 中的默认生成函数的表。
SQLAlchemy 也支持非 DDL 服务器端的默认设置,如客户端调用的 SQL 表达式文档中所述;这些“客户端调用的 SQL 表达式”是使用Column.default
和Column.onupdate
参数设置的。
目前,ORM 中的这些 SQL 表达式受到与真正的服务器端默认值相同的限制;当 Mapper.eager_defaults
设置为 "auto"
或 True
时,除非 FetchedValue
指令与 Column
相关联,否则它们不会被 RETURNING 急切地获取,尽管这些表达式不是 DDL 服务器默认值,并且由 SQLAlchemy 本身主动渲染。这个限制可能在未来的 SQLAlchemy 版本中得到解决。
FetchedValue
构造可以同时应用于 Column.server_default
或 Column.server_onupdate
,就像下面的例子中使用 func.now()
构造作为客户端调用的 SQL 表达式用于 Column.default
和 Column.onupdate
一样。为了使 Mapper.eager_defaults
的行为包括在可用时使用 RETURNING 获取这些值,Column.server_default
和 Column.server_onupdate
与 FetchedValue
一起使用以确保获取发生:
class MyModel(Base): __tablename__ = "my_table" id = mapped_column(Integer, primary_key=True) created = mapped_column( DateTime(), default=func.now(), server_default=FetchedValue() ) updated = mapped_column( DateTime(), onupdate=func.now(), server_default=FetchedValue(), server_onupdate=FetchedValue(), ) __mapper_args__ = {"eager_defaults": True}
使用类似上面的映射,ORM 渲染的 INSERT 和 UPDATE 的 SQL 将在 RETURNING 子句中包括created
和updated
:
INSERT INTO my_table (created) VALUES (now()) RETURNING my_table.id, my_table.created, my_table.updated UPDATE my_table SET updated=now() WHERE my_table.id = %(my_table_id)s RETURNING my_table.updated
情况 1:非主键,支持 RETURNING 或等效功能
在这种情况下,应将列标记为FetchedValue
或具有显式的Column.server_default
。如果Mapper.eager_defaults
参数设置为True
,或者对于支持 RETURNING 以及 insertmanyvalues 的方言,默认设置为"auto"
,ORM 将在执行 INSERT 语句时自动将这些列添加到 RETURNING 子句中:
class MyModel(Base): __tablename__ = "my_table" id = mapped_column(Integer, primary_key=True) # server-side SQL date function generates a new timestamp timestamp = mapped_column(DateTime(), server_default=func.now()) # some other server-side function not named here, such as a trigger, # populates a value into this column during INSERT special_identifier = mapped_column(String(50), server_default=FetchedValue()) # set eager defaults to True. This is usually optional, as if the # backend supports RETURNING + insertmanyvalues, eager defaults # will take place regardless on INSERT __mapper_args__ = {"eager_defaults": True}
在上述情况下,未在客户端指定“timestamp”或“special_identifier”的显式值的 INSERT 语句将包括“timestamp”和“special_identifier”列在 RETURNING 子句中,以便立即使用。在 PostgreSQL 数据库上,上述表的 INSERT 将如下所示:
INSERT INTO my_table DEFAULT VALUES RETURNING my_table.id, my_table.timestamp, my_table.special_identifier
从版本 2.0.0rc1 开始更改:Mapper.eager_defaults
参数现在默认为新设置"auto"
,如果支持 RETURNING 以及 insertmanyvalues 的后端数据库,则会自动使用 RETURNING 来获取 INSERT 时的服务器生成默认值。
注意
Mapper.eager_defaults
的"auto"
值仅适用于 INSERT 语句。即使可用,UPDATE 语句也不会使用 RETURNING,除非将Mapper.eager_defaults
设置为True
。这是因为 UPDATE 没有等效的“insertmanyvalues”特性,因此 UPDATE RETURNING 将要求为每个被 UPDATE 的行分别发出 UPDATE 语句。
情况 2:表包含与 RETURNING 不兼容的触发器生成的值
Mapper.eager_defaults
的"auto"
设置意味着支持 RETURNING 的后端通常会在 INSERT 语句中使用 RETURNING 来检索新生成的默认值。但是,存在使用触发器生成的服务器生成值的限制,因此不能使用 RETURNING:
- SQL Server 不允许在 INSERT 语句中使用 RETURNING 来检索触发器生成的值;该语句将失败。
- SQLite 在与触发器结合使用 RETURNING 时存在限制,因此 RETURNING 子句将无法获取已插入的值。
- 其他后端可能在与触发器或其他类型的服务器生成值结合使用 RETURNING 时存在限制。
要禁用用于此类值的 RETURNING 的使用,包括不仅用于服务器生成的默认值而且确保 ORM 永远不会使用 RETURNING 与特定表,指定 Table.implicit_returning
为 False
对于映射的 Table
。使用声明性映射,看起来像这样:
class MyModel(Base): __tablename__ = "my_table" id: Mapped[int] = mapped_column(primary_key=True) data: Mapped[str] = mapped_column(String(50)) # assume a database trigger populates a value into this column # during INSERT special_identifier = mapped_column(String(50), server_default=FetchedValue()) # disable all use of RETURNING for the table __table_args__ = {"implicit_returning": False}
在使用 pyodbc 驱动程序的 SQL Server 上,对于上述表的 INSERT 不会使用 RETURNING,并且将使用 SQL Server scope_identity()
函数来检索新生成的主键值:
INSERT INTO my_table (data) VALUES (?); select scope_identity()
另请参阅
INSERT 行为 - 关于 SQL Server 方言获取新生成的主键值的方法的背景
情况 3:非主键,不支持或不需要 RETURNING 或等效功能
该情况与上面的情况 1 相同,只是我们通常不想使用 Mapper.eager_defaults
,因为在没有 RETURNING 支持的情况下,其当前实现是发出每行一个 SELECT,这是不高效的。因此,在下面的映射中省略了该参数:
class MyModel(Base): __tablename__ = "my_table" id = mapped_column(Integer, primary_key=True) timestamp = mapped_column(DateTime(), server_default=func.now()) # assume a database trigger populates a value into this column # during INSERT special_identifier = mapped_column(String(50), server_default=FetchedValue())
在上述映射中插入记录后,在不包括 RETURNING 或“insertmanyvalues”支持的后端上,“timestamp” 和 “special_identifier” 列将保持为空,并且在刷新后首次访问时将通过第二个 SELECT 语句获取,例如,它们被标记为“过期”时。
如果 Mapper.eager_defaults
明确提供了值 True
,并且后端数据库不支持 RETURNING 或等效功能,则 ORM 将在 INSERT 语句后立即发出 SELECT 语句,以获取新生成的值;如果没有可用的 RETURNING,ORM 目前无法批量选择许多新插入的行。这通常是不可取的,因为它会向刷新过程添加额外的 SELECT 语句,这些语句可能是不需要的。使用上述映射,针对 MySQL(不是 MariaDB)将 Mapper.eager_defaults
标志设置为 True 在刷新时会产生类似以下的 SQL:
INSERT INTO my_table () VALUES () -- when eager_defaults **is** used, but RETURNING is not supported SELECT my_table.timestamp AS my_table_timestamp, my_table.special_identifier AS my_table_special_identifier FROM my_table WHERE my_table.id = %s
未来的 SQLAlchemy 版本可能会在没有 RETURNING 的情况下寻求改进急切默认值的效率,以在单个 SELECT 语句中批量处理多行。
情况 4:主键,支持 RETURNING 或等效功能
具有服务器生成值的主键列必须在 INSERT 后立即获取;ORM 只能访问具有主键值的行,因此如果主键由服务器生成,则 ORM 需要一种在 INSERT 后立即检索该新值的方法。
如上所述,对于整数“自增”列,以及标记为Identity
的列和特殊构造,例如 PostgreSQL 的 SERIAL,这些类型将由核心自动处理;数据库包括获取“最后插入的 id”函数,其中不支持 RETURNING,而在支持 RETURNING 的情况下,SQLAlchemy 将使用它。
例如,使用 Oracle 并将列标记为Identity
,RETURNING 将自动用于获取新的主键值:
class MyOracleModel(Base): __tablename__ = "my_table" id: Mapped[int] = mapped_column(Identity(), primary_key=True) data: Mapped[str] = mapped_column(String(50))
如上模型在 Oracle 上的 INSERT 如下所示:
INSERT INTO my_table (data) VALUES (:data) RETURNING my_table.id INTO :ret_0
SQLAlchemy 渲染“data”字段的 INSERT,但仅在 RETURNING 子句中包含“id”,以便在服务器端生成“id”并立即返回新值。
对于由服务器端函数或触发器生成的非整数值,以及来自表本身之外的构造的整数值,包括显式序列和触发器,必须在表元数据中标记服务器默认生成。再次以 Oracle 为例,我们可以用Sequence
构造说明一个类似的表:
class MyOracleModel(Base): __tablename__ = "my_table" id: Mapped[int] = mapped_column(Sequence("my_oracle_seq"), primary_key=True) data: Mapped[str] = mapped_column(String(50))
Oracle 上此模型的 INSERT 如下所示:
INSERT INTO my_table (id, data) VALUES (my_oracle_seq.nextval, :data) RETURNING my_table.id INTO :ret_0
在上述情况下,SQLAlchemy 渲染my_sequence.nextval
用于主键列的新主键生成,并且还使用 RETURNING 立即获取新值。
如果数据源不是由简单的 SQL 函数或Sequence
表示,例如使用触发器或生成新值的数据库特定数据类型,可以通过在列定义中使用FetchedValue
来指示值生成的默认值。下面是一个使用 SQL Server TIMESTAMP 列作为主键的模型;在 SQL Server 上,此数据类型会自动生成新值,因此在表元数据中通过为Column.server_default
参数指定FetchedValue
来指示:
class MySQLServerModel(Base): __tablename__ = "my_table" timestamp: Mapped[datetime.datetime] = mapped_column( TIMESTAMP(), server_default=FetchedValue(), primary_key=True ) data: Mapped[str] = mapped_column(String(50))
SQL Server 上上述表的 INSERT 如下所示:
INSERT INTO my_table (data) OUTPUT inserted.timestamp VALUES (?)
情况 5:不支持主键、RETURNING 或等效项。
在这个领域,我们为像 MySQL 这样的数据库生成行,其中服务器上正在发生某种默认生成的方法,但是超出了数据库的通常自动增量例程。在这种情况下,我们必须确保 SQLAlchemy 可以“预执行”默认值,这意味着它必须是一个显式的 SQL 表达式。
注
本节将说明 MySQL 中涉及日期时间值的多个示例,因为此后端的日期时间数据类型具有有用的额外特殊要求。但是请记住,MySQL 对于用作主键的任何自动生成的数据类型都需要明确的“预执行”默认生成器,除了通常的单列自增整数值。
MySQL 使用 DateTime 主键
使用 MySQL 的DateTime
列作为例子,我们使用“NOW()”SQL 函数添加一个明确的预执行支持的默认值:
class MyModel(Base): __tablename__ = "my_table" timestamp = mapped_column(DateTime(), default=func.now(), primary_key=True)
在上面的例子中,我们选择“NOW()”函数将日期时间值传递给列。由上述生成的 SQL 是:
SELECT now() AS anon_1 INSERT INTO my_table (timestamp) VALUES (%s) ('2018-08-09 13:08:46',)
MySQL 使用 TIMESTAMP 主键
当在 MySQL 中使用TIMESTAMP
数据类型时,MySQL 通常会自动将服务器端默认值与此数据类型关联起来。但是,当我们将其用作主键时,Core 无法检索到新生成的值,除非我们自己执行该函数。由于 MySQL 上的TIMESTAMP
实际上存储的是二进制值,因此我们需要在“NOW()”的使用中添加额外的“CAST”,以便检索到可持久化到列中的二进制值:
from sqlalchemy import cast, Binary class MyModel(Base): __tablename__ = "my_table" timestamp = mapped_column( TIMESTAMP(), default=cast(func.now(), Binary), primary_key=True )
上述,除了选择“NOW()”函数外,我们还额外利用Binary
数据类型与cast()
结合使用,以便返回值是二进制的。在 INSERT 中生成的 SQL 如下所示:
SELECT CAST(now() AS BINARY) AS anon_1 INSERT INTO my_table (timestamp) VALUES (%s) (b'2018-08-09 13:08:46',)
另请参阅
列的 INSERT/UPDATE 默认值
MySQL 使用 DateTime 主键
使用 MySQL 的DateTime
列作为例子,我们使用“NOW()”SQL 函数添加一个明确的预执行支持的默认值:
class MyModel(Base): __tablename__ = "my_table" timestamp = mapped_column(DateTime(), default=func.now(), primary_key=True)
在上面的例子中,我们选择“NOW()”函数将日期时间值传递给列。由上述生成的 SQL 是:
SELECT now() AS anon_1 INSERT INTO my_table (timestamp) VALUES (%s) ('2018-08-09 13:08:46',)
MySQL 使用 TIMESTAMP 主键
当在 MySQL 中使用TIMESTAMP
数据类型时,MySQL 通常会自动将服务器端默认值与此数据类型关联起来。但是,当我们将其用作主键时,Core 无法检索到新生成的值,除非我们自己执行该函数。由于 MySQL 上的TIMESTAMP
实际上存储的是二进制值,因此我们需要在“NOW()”的使用中添加额外的“CAST”,以便检索到可持久化到列中的二进制值:
from sqlalchemy import cast, Binary class MyModel(Base): __tablename__ = "my_table" timestamp = mapped_column( TIMESTAMP(), default=cast(func.now(), Binary), primary_key=True )
在上面的示例中,除了选择“NOW()”函数外,我们还使用Binary
数据类型结合cast()
,以使返回的值是二进制的。在 INSERT 中从上面渲染的 SQL 如下所示:
SELECT CAST(now() AS BINARY) AS anon_1 INSERT INTO my_table (timestamp) VALUES (%s) (b'2018-08-09 13:08:46',)
另请参阅
列插入/更新默认值
关于急切获取用于 INSERT 或 UPDATE 的客户端调用的 SQL 表达式的注意事项
前面的示例表明了使用Column.server_default
创建包含默认生成函数的表的方法。
SQLAlchemy 也支持非 DDL 服务器端默认值,如客户端调用的 SQL 表达式文档所述;这些“客户端调用的 SQL 表达式”是使用Column.default
和Column.onupdate
参数设置的。
这些 SQL 表达式目前受 ORM 中与真正的服务器端默认值发生的相同限制的约束;当Mapper.eager_defaults
设置为"auto"
或True
时,它们不会被急切地获取到 RETURNING 中,除非FetchedValue
指令与Column
关联,即使这些表达式不是 DDL 服务器默认值,而是由 SQLAlchemy 本身主动渲染的。这个限制可能在未来的 SQLAlchemy 版本中得到解决。
FetchedValue
构造可以同时应用于 Column.server_default
或 Column.server_onupdate
,与 Column.default
和 Column.onupdate
一起使用 SQL 表达式,例如下面的示例中,func.now()
构造被用作客户端调用的 SQL 表达式,用于 Column.default
和 Column.onupdate
。为了使 Mapper.eager_defaults
的行为包括使用 RETURNING 在可用时获取这些值,需要使用 Column.server_default
和 Column.server_onupdate
与 FetchedValue
以确保获取发生:
class MyModel(Base): __tablename__ = "my_table" id = mapped_column(Integer, primary_key=True) created = mapped_column( DateTime(), default=func.now(), server_default=FetchedValue() ) updated = mapped_column( DateTime(), onupdate=func.now(), server_default=FetchedValue(), server_onupdate=FetchedValue(), ) __mapper_args__ = {"eager_defaults": True}
与上述类似的映射,ORM 渲染的 INSERT 和 UPDATE 的 SQL 将在 RETURNING 子句中包括 created
和 updated
:
INSERT INTO my_table (created) VALUES (now()) RETURNING my_table.id, my_table.created, my_table.updated UPDATE my_table SET updated=now() WHERE my_table.id = %(my_table_id)s RETURNING my_table.updated
SqlAlchemy 2.0 中文文档(二十四)(3)https://developer.aliyun.com/article/1560546