SqlAlchemy 2.0 中文文档(二十四)(2)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介: SqlAlchemy 2.0 中文文档(二十四)

SqlAlchemy 2.0 中文文档(二十四)(1)https://developer.aliyun.com/article/1560544


使用 SQL 表达式与会话

SQL 表达式和字符串可以通过其事务上下文在 Session 中执行。这最容易通过 Session.execute() 方法来实现,该方法以与 EngineConnection 相同的方式返回一个 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()

上面的示例涉及到绑定到单个 EngineConnectionSession。要使用绑定到多个引擎或根本没有绑定到引擎的 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()mapperclause 参数现在作为字典的一部分发送,作为 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_returningFalse。使用声明性映射如下所示:

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.defaultColumn.onupdate参数设置的。

目前,ORM 中的这些 SQL 表达式受到与真正的服务器端默认值相同的限制;当 Mapper.eager_defaults 设置为 "auto"True 时,除非 FetchedValue 指令与 Column 相关联,否则它们不会被 RETURNING 急切地获取,尽管这些表达式不是 DDL 服务器默认值,并且由 SQLAlchemy 本身主动渲染。这个限制可能在未来的 SQLAlchemy 版本中得到解决。

FetchedValue 构造可以同时应用于 Column.server_defaultColumn.server_onupdate,就像下面的例子中使用 func.now() 构造作为客户端调用的 SQL 表达式用于 Column.defaultColumn.onupdate 一样。为了使 Mapper.eager_defaults 的行为包括在可用时使用 RETURNING 获取这些值,Column.server_defaultColumn.server_onupdateFetchedValue 一起使用以确保获取发生:

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 子句中包括createdupdated

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_returningFalse 对于映射的 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.defaultColumn.onupdate参数设置的。

这些 SQL 表达式目前受 ORM 中与真正的服务器端默认值发生的相同限制的约束;当Mapper.eager_defaults设置为"auto"True时,它们不会被急切地获取到 RETURNING 中,除非FetchedValue指令与Column关联,即使这些表达式不是 DDL 服务器默认值,而是由 SQLAlchemy 本身主动渲染的。这个限制可能在未来的 SQLAlchemy 版本中得到解决。

FetchedValue 构造可以同时应用于 Column.server_defaultColumn.server_onupdate,与 Column.defaultColumn.onupdate 一起使用 SQL 表达式,例如下面的示例中,func.now() 构造被用作客户端调用的 SQL 表达式,用于 Column.defaultColumn.onupdate。为了使 Mapper.eager_defaults 的行为包括使用 RETURNING 在可用时获取这些值,需要使用 Column.server_defaultColumn.server_onupdateFetchedValue 以确保获取发生:

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 子句中包括 createdupdated

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

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
4月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(二十九)(3)
SqlAlchemy 2.0 中文文档(二十九)
44 4
|
4月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(二十九)(2)
SqlAlchemy 2.0 中文文档(二十九)
39 7
|
4月前
|
SQL 存储 关系型数据库
SqlAlchemy 2.0 中文文档(二十九)(1)
SqlAlchemy 2.0 中文文档(二十九)
51 4
|
4月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(二十九)(4)
SqlAlchemy 2.0 中文文档(二十九)
43 4
|
4月前
|
SQL 数据库连接 API
SqlAlchemy 2.0 中文文档(二十八)(1)
SqlAlchemy 2.0 中文文档(二十八)
123 2
|
4月前
|
SQL 存储 测试技术
SqlAlchemy 2.0 中文文档(二十)(3)
SqlAlchemy 2.0 中文文档(二十)
31 1
|
4月前
|
SQL 缓存 API
SqlAlchemy 2.0 中文文档(二十)(5)
SqlAlchemy 2.0 中文文档(二十)
29 1
|
4月前
|
SQL 测试技术 API
SqlAlchemy 2.0 中文文档(二十)(4)
SqlAlchemy 2.0 中文文档(二十)
31 1
|
4月前
|
SQL 缓存 API
SqlAlchemy 2.0 中文文档(二十八)(4)
SqlAlchemy 2.0 中文文档(二十八)
77 1
|
4月前
|
关系型数据库 测试技术 API
SqlAlchemy 2.0 中文文档(二十八)(3)
SqlAlchemy 2.0 中文文档(二十八)
41 1