SqlAlchemy 2.0 中文文档(四十)(4)

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

SqlAlchemy 2.0 中文文档(四十)(3)https://developer.aliyun.com/article/1563002


定义外键

在 SQL 中,外键是一个表级构造,它限制该表中的一个或多个列只允许存在于另一组列中的值,通常但不总是位于不同的表中。我们将受到限制的列称为外键列,它们被约束到的列称为引用列。引用列几乎总是定义其所属表的主键,尽管也有例外情况。外键是连接具有彼此关系的行对的“接头部分”,在几乎每个操作中,SQLAlchemy 都将这个概念赋予了非常重要的意义。

在 SQLAlchemy 以及 DDL 中,外键约束可以作为表子句中的附加属性来定义,或者对于单列外键,它们可以选择地在单列的定义中指定。单列外键更常见,在列级别上通过将 ForeignKey 对象构造为 Column 对象的参数来指定:

user_preference = Table(
    "user_preference",
    metadata_obj,
    Column("pref_id", Integer, primary_key=True),
    Column("user_id", Integer, ForeignKey("user.user_id"), nullable=False),
    Column("pref_name", String(40), nullable=False),
    Column("pref_value", String(100)),
)

在上面的示例中,我们为 user_preference 定义了一个新表,其中每行必须包含一个存在于 user 表的 user_id 列中的值。

ForeignKey 的参数最常见的是形式为 . 的字符串,或者对于远程架构或“拥有者”的表,形式为 ..。它也可以是一个实际的 Column 对象,稍后我们将看到,它通过其 c 集合从现有的 Table 对象中访问:

ForeignKey(user.c.user_id)

使用字符串的优势在于,useruser_preference 之间的 python 链接只有在首次需要时才会解析,因此表对象可以轻松地分布在多个模块中,并且以任何顺序定义。

外键也可以在表级别定义,使用ForeignKeyConstraint对象。此对象可以描述单列或多列外键。多列外键被称为复合外键,并且几乎总是引用具有复合主键的表。下面我们定义一个具有复合主键的invoice表:

invoice = Table(
    "invoice",
    metadata_obj,
    Column("invoice_id", Integer, primary_key=True),
    Column("ref_num", Integer, primary_key=True),
    Column("description", String(60), nullable=False),
)

然后一个具有引用invoice的复合外键的invoice_item表:

invoice_item = Table(
    "invoice_item",
    metadata_obj,
    Column("item_id", Integer, primary_key=True),
    Column("item_name", String(60), nullable=False),
    Column("invoice_id", Integer, nullable=False),
    Column("ref_num", Integer, nullable=False),
    ForeignKeyConstraint(
        ["invoice_id", "ref_num"], ["invoice.invoice_id", "invoice.ref_num"]
    ),
)

需要注意的是,ForeignKeyConstraint 是定义复合外键的唯一方法。虽然我们也可以将单独的 ForeignKey 对象放置在invoice_item.invoice_idinvoice_item.ref_num列上,但 SQLAlchemy 不会意识到这两个值应该成对出现 - 它将成为两个单独的外键约束,而不是引用两列的单个复合外键。

通过 ALTER 创建/删除外键约束

我们在教程和其他地方看到的关于 DDL 中外键的行为表明,约束通常以“内联”的方式在 CREATE TABLE 语句中呈现,例如:

CREATE  TABLE  addresses  (
  id  INTEGER  NOT  NULL,
  user_id  INTEGER,
  email_address  VARCHAR  NOT  NULL,
  PRIMARY  KEY  (id),
  CONSTRAINT  user_id_fk  FOREIGN  KEY(user_id)  REFERENCES  users  (id)
)

CONSTRAINT .. FOREIGN KEY指令用于在 CREATE TABLE 定义内“内联”创建约束。MetaData.create_all()MetaData.drop_all() 方法默认使用拓扑排序对所有涉及的 Table 对象进行排序,以便按外键依赖关系的顺序创建和删除表(此排序也可通过 MetaData.sorted_tables 访问器获得)。

当涉及两个或更多外键约束的“依赖循环”时,此方法无法工作,在这种情况下,一组表彼此相互依赖,假设后端执行外键(SQLite  除外,MySQL/MyISAM 总是如此)。因此,该方法将在除 SQLite 之外的所有后端上将循环中的约束分解为单独的 ALTER 语句,因为  SQLite 不支持大多数形式的 ALTER。给定一个类似的模式:

node = Table(
    "node",
    metadata_obj,
    Column("node_id", Integer, primary_key=True),
    Column("primary_element", Integer, ForeignKey("element.element_id")),
)
element = Table(
    "element",
    metadata_obj,
    Column("element_id", Integer, primary_key=True),
    Column("parent_node_id", Integer),
    ForeignKeyConstraint(
        ["parent_node_id"], ["node.node_id"], name="fk_element_parent_node_id"
    ),
)

当我们在后端(如 PostgreSQL 后端)调用MetaData.create_all()时,这两个表之间的循环被解决,约束被分别创建:

>>> with engine.connect() as conn:
...     metadata_obj.create_all(conn, checkfirst=False)
CREATE  TABLE  element  (
  element_id  SERIAL  NOT  NULL,
  parent_node_id  INTEGER,
  PRIMARY  KEY  (element_id)
)
CREATE  TABLE  node  (
  node_id  SERIAL  NOT  NULL,
  primary_element  INTEGER,
  PRIMARY  KEY  (node_id)
)
ALTER  TABLE  element  ADD  CONSTRAINT  fk_element_parent_node_id
  FOREIGN  KEY(parent_node_id)  REFERENCES  node  (node_id)
ALTER  TABLE  node  ADD  FOREIGN  KEY(primary_element)
  REFERENCES  element  (element_id) 

为了对这些表发出 DROP,相同的逻辑适用,但是请注意,在 SQL 中,发出 DROP CONSTRAINT 需要约束具有名称。 在上述'node'表的情况下,我们没有命名此约束; 因此,系统将仅尝试为具有名称的约束发出 DROP:

>>> with engine.connect() as conn:
...     metadata_obj.drop_all(conn, checkfirst=False)
ALTER  TABLE  element  DROP  CONSTRAINT  fk_element_parent_node_id
DROP  TABLE  node
DROP  TABLE  element 

在无法解析循环的情况下,例如,如果我们没有在这里为任一约束指定名称,我们将收到以下错误:

sqlalchemy.exc.CircularDependencyError: Can't sort tables for DROP;
an unresolvable foreign key dependency exists between tables:
element, node.  Please ensure that the ForeignKey and ForeignKeyConstraint
objects involved in the cycle have names so that they can be dropped
using DROP CONSTRAINT.

此错误仅适用于 DROP 情况,因为在 CREATE 情况下我们可以不带名称发出“ADD CONSTRAINT”; 数据库通常会自动分配一个名称。

当手动解析依赖关系循环时,可以使用ForeignKeyConstraint.use_alterForeignKey.use_alter 关键字参数。 我们可以仅将此标志添加到'element'表中,如下所示:

element = Table(
    "element",
    metadata_obj,
    Column("element_id", Integer, primary_key=True),
    Column("parent_node_id", Integer),
    ForeignKeyConstraint(
        ["parent_node_id"],
        ["node.node_id"],
        use_alter=True,
        name="fk_element_parent_node_id",
    ),
)

在我们的 CREATE DDL 中,我们将只看到这个约束的 ALTER 语句,而不是其他约束:

>>> with engine.connect() as conn:
...     metadata_obj.create_all(conn, checkfirst=False)
CREATE  TABLE  element  (
  element_id  SERIAL  NOT  NULL,
  parent_node_id  INTEGER,
  PRIMARY  KEY  (element_id)
)
CREATE  TABLE  node  (
  node_id  SERIAL  NOT  NULL,
  primary_element  INTEGER,
  PRIMARY  KEY  (node_id),
  FOREIGN  KEY(primary_element)  REFERENCES  element  (element_id)
)
ALTER  TABLE  element  ADD  CONSTRAINT  fk_element_parent_node_id
FOREIGN  KEY(parent_node_id)  REFERENCES  node  (node_id) 

当与删除操作一起使用时,ForeignKeyConstraint.use_alterForeignKey.use_alter 关键字参数将要求约束具有名称,否则将生成以下错误:

sqlalchemy.exc.CompileError: Can't emit DROP CONSTRAINT for constraint
ForeignKeyConstraint(...); it has no name

请参见

配置约束命名约定

sort_tables_and_constraints() ### ON UPDATE and ON DELETE

大多数数据库支持外键值的级联,即当父行更新时,新值将放置在子行中,或者当父行删除时,所有相应的子行都将设置为 null 或删除。  在数据定义语言中,这些是使用诸如“ON UPDATE CASCADE”,“ON DELETE CASCADE”和“ON DELETE SET  NULL”之类的短语来指定的,这些短语对应于外键约束。 “ON UPDATE”或“ON  DELETE”后面的短语还可以允许其他与正在使用的数据库特定的短语相对应的短语。 ForeignKeyForeignKeyConstraint 对象通过 onupdateondelete 关键字参数支持通过生成此子句。 值是任何字符串,将在适当的“ON UPDATE”或“ON DELETE”短语之后输出:

child = Table(
    "child",
    metadata_obj,
    Column(
        "id",
        Integer,
        ForeignKey("parent.id", onupdate="CASCADE", ondelete="CASCADE"),
        primary_key=True,
    ),
)
composite = Table(
    "composite",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("rev_id", Integer),
    Column("note_id", Integer),
    ForeignKeyConstraint(
        ["rev_id", "note_id"],
        ["revisions.id", "revisions.note_id"],
        onupdate="CASCADE",
        ondelete="SET NULL",
    ),
)

请注意,这些子句在与 MySQL 一起使用时需要InnoDB表。 它们在其他数据库上也可能不受支持。

请参见

关于与 ORM relationship() 构造的 ON DELETE CASCADE 集成的背景,请参见以下各节:

使用 ORM 关系中的外键 ON DELETE cascade

在多对多关系中使用外键 ON DELETE ### 通过 ALTER 创建/删除外键约束

我们在教程和其他地方看到的涉及 DDL 的外键的行为表明,约束通常在 CREATE TABLE 语句中“内联”呈现,例如:

CREATE  TABLE  addresses  (
  id  INTEGER  NOT  NULL,
  user_id  INTEGER,
  email_address  VARCHAR  NOT  NULL,
  PRIMARY  KEY  (id),
  CONSTRAINT  user_id_fk  FOREIGN  KEY(user_id)  REFERENCES  users  (id)
)

CONSTRAINT .. FOREIGN KEY 指令用于以“内联”方式在 CREATE TABLE 定义中创建约束。 MetaData.create_all()MetaData.drop_all() 方法默认使用所有涉及的 Table 对象的拓扑排序,这样表就按照它们的外键依赖顺序创建和删除(此排序也可以通过 MetaData.sorted_tables 访问器获得)。

当涉及两个或更多个外键约束参与“依赖循环”时,此方法无法工作,在此循环中一组表相互依赖,假设后端强制执行外键(除  SQLite、MySQL/MyISAM 之外的情况始终如此)。因此,方法将在除不支持大多数 ALTER 形式的 SQLite  外的所有后端上将此类循环中的约束拆分为单独的 ALTER 语句。给定这样的模式:

node = Table(
    "node",
    metadata_obj,
    Column("node_id", Integer, primary_key=True),
    Column("primary_element", Integer, ForeignKey("element.element_id")),
)
element = Table(
    "element",
    metadata_obj,
    Column("element_id", Integer, primary_key=True),
    Column("parent_node_id", Integer),
    ForeignKeyConstraint(
        ["parent_node_id"], ["node.node_id"], name="fk_element_parent_node_id"
    ),
)

当我们在诸如 PostgreSQL 后端之类的后端上调用 MetaData.create_all() 时,这两个表之间的循环被解决,并且约束被单独创建:

>>> with engine.connect() as conn:
...     metadata_obj.create_all(conn, checkfirst=False)
CREATE  TABLE  element  (
  element_id  SERIAL  NOT  NULL,
  parent_node_id  INTEGER,
  PRIMARY  KEY  (element_id)
)
CREATE  TABLE  node  (
  node_id  SERIAL  NOT  NULL,
  primary_element  INTEGER,
  PRIMARY  KEY  (node_id)
)
ALTER  TABLE  element  ADD  CONSTRAINT  fk_element_parent_node_id
  FOREIGN  KEY(parent_node_id)  REFERENCES  node  (node_id)
ALTER  TABLE  node  ADD  FOREIGN  KEY(primary_element)
  REFERENCES  element  (element_id) 

为了发出这些表的 DROP 命令,相同的逻辑适用,但是请注意,SQL 中,要发出 DROP CONSTRAINT 需要约束具有名称。在上面的 'node' 表的情况下,我们没有为此约束命名;因此系统将尝试仅发出命名的约束的 DROP:

>>> with engine.connect() as conn:
...     metadata_obj.drop_all(conn, checkfirst=False)
ALTER  TABLE  element  DROP  CONSTRAINT  fk_element_parent_node_id
DROP  TABLE  node
DROP  TABLE  element 

如果无法解决循环,例如我们在这里未给任何约束应用名称的情况,我们将收到以下错误:

sqlalchemy.exc.CircularDependencyError: Can't sort tables for DROP;
an unresolvable foreign key dependency exists between tables:
element, node.  Please ensure that the ForeignKey and ForeignKeyConstraint
objects involved in the cycle have names so that they can be dropped
using DROP CONSTRAINT.

此错误仅适用于 DROP 案例,因为我们可以在 CREATE 案例中发出“ADD CONSTRAINT”而无需名称;数据库通常会自动分配一个名称。

ForeignKeyConstraint.use_alterForeignKey.use_alter 关键字参数可用于手动解决依赖循环。我们可以将此标志仅添加到 'element' 表中,如下所示:

element = Table(
    "element",
    metadata_obj,
    Column("element_id", Integer, primary_key=True),
    Column("parent_node_id", Integer),
    ForeignKeyConstraint(
        ["parent_node_id"],
        ["node.node_id"],
        use_alter=True,
        name="fk_element_parent_node_id",
    ),
)

在我们的 CREATE DDL 中,我们将只看到该约束的 ALTER 语句,而不是其他的:

>>> with engine.connect() as conn:
...     metadata_obj.create_all(conn, checkfirst=False)
CREATE  TABLE  element  (
  element_id  SERIAL  NOT  NULL,
  parent_node_id  INTEGER,
  PRIMARY  KEY  (element_id)
)
CREATE  TABLE  node  (
  node_id  SERIAL  NOT  NULL,
  primary_element  INTEGER,
  PRIMARY  KEY  (node_id),
  FOREIGN  KEY(primary_element)  REFERENCES  element  (element_id)
)
ALTER  TABLE  element  ADD  CONSTRAINT  fk_element_parent_node_id
FOREIGN  KEY(parent_node_id)  REFERENCES  node  (node_id) 

当与删除操作一起使用时,ForeignKeyConstraint.use_alterForeignKey.use_alter 需要命名约束,否则会生成以下错误:

sqlalchemy.exc.CompileError: Can't emit DROP CONSTRAINT for constraint
ForeignKeyConstraint(...); it has no name

另见

配置约束命名约定

sort_tables_and_constraints()

ON UPDATE 和 ON DELETE

大多数数据库支持外键值的级联,也就是当父行更新时,新值将放置在子行中,或者当父行删除时,所有相应的子行都设置为 null  或删除。在数据定义语言中,这些是使用诸如“ON UPDATE CASCADE”、“ON DELETE CASCADE”和“ON DELETE  SET NULL”之类的短语指定的,对应于外键约束。在“ON UPDATE”或“ON  DELETE”之后的短语可能还允许其他特定于正在使用的数据库的短语。 ForeignKeyForeignKeyConstraint 对象支持通过 onupdateondelete 关键字参数生成此子句。该值是任何字符串,将在适当的“ON UPDATE”或“ON DELETE”短语之后输出:

child = Table(
    "child",
    metadata_obj,
    Column(
        "id",
        Integer,
        ForeignKey("parent.id", onupdate="CASCADE", ondelete="CASCADE"),
        primary_key=True,
    ),
)
composite = Table(
    "composite",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("rev_id", Integer),
    Column("note_id", Integer),
    ForeignKeyConstraint(
        ["rev_id", "note_id"],
        ["revisions.id", "revisions.note_id"],
        onupdate="CASCADE",
        ondelete="SET NULL",
    ),
)

请注意,当与 MySQL 一起使用时,这些子句需要 InnoDB 表。它们在其他数据库上也可能不受支持。

另见

关于将 ON DELETE CASCADE 与 ORM relationship() 构造集成的背景信息,请参见以下部分:

使用 ORM 关联的外键 ON DELETE cascade

在多对多关系中使用外键 ON DELETE

唯一约束

可以使用 Column 上的 unique 关键字匿名地在单个列上创建唯一约束。通过 UniqueConstraint 表级构造显式命名的唯一约束和/或具有多列的约束。

from sqlalchemy import UniqueConstraint
metadata_obj = MetaData()
mytable = Table(
    "mytable",
    metadata_obj,
    # per-column anonymous unique constraint
    Column("col1", Integer, unique=True),
    Column("col2", Integer),
    Column("col3", Integer),
    # explicit/composite unique constraint.  'name' is optional.
    UniqueConstraint("col2", "col3", name="uix_1"),
)

CHECK 约束

检查约束可以具有命名或未命名,并且可以在列级别或表级别上创建,使用 CheckConstraint 构造。检查约束的文本直接传递到数据库,因此具有有限的“数据库独立”行为。列级别的检查约束通常只应引用它们所放置的列,而表级别的约束可以引用表中的任何列。

请注意,一些数据库不支持主动支持检查约束,例如较旧版本的 MySQL(在 8.0.16 之前)。

from sqlalchemy import CheckConstraint
metadata_obj = MetaData()
mytable = Table(
    "mytable",
    metadata_obj,
    # per-column CHECK constraint
    Column("col1", Integer, CheckConstraint("col1>5")),
    Column("col2", Integer),
    Column("col3", Integer),
    # table level CHECK constraint.  'name' is optional.
    CheckConstraint("col2 > col3 + 5", name="check1"),
)
mytable.create(engine)
CREATE  TABLE  mytable  (
  col1  INTEGER  CHECK  (col1>5),
  col2  INTEGER,
  col3  INTEGER,
  CONSTRAINT  check1  CHECK  (col2  >  col3  +  5)
) 

主键约束

任何 Table 对象的主键约束都是隐式存在的,基于标记有 Column.primary_key 标志的 Column 对象。PrimaryKeyConstraint 对象提供对此约束的显式访问,包括直接配置的选项:

from sqlalchemy import PrimaryKeyConstraint
my_table = Table(
    "mytable",
    metadata_obj,
    Column("id", Integer),
    Column("version_id", Integer),
    Column("data", String(50)),
    PrimaryKeyConstraint("id", "version_id", name="mytable_pk"),
)

另请参阅

PrimaryKeyConstraint - 详细的 API 文档。

使用 Declarative ORM 扩展时设置约束

Table 是 SQLAlchemy 核心的构造,允许定义表元数据,这些元数据可以被 SQLAlchemy ORM 用作映射类的目标之一。Declarative 扩展允许自动创建 Table 对象,主要是将表的内容作为 Column 对象的映射。

要将诸如 ForeignKeyConstraint 等表级约束对象应用于使用 Declarative 定义的表,请使用 __table_args__ 属性,详见 表配置。

配置约束命名约定

关系数据库通常为所有约束和索引分配显式名称。在创建表时使用CREATE TABLE的常见情况下,约束(如 CHECK、UNIQUE 和 PRIMARY KEY 约束)会与表定义一起内联生成,如果未另有规定,则数据库通常会自动分配名称给这些约束。在使用诸如ALTER TABLE之类的命令在数据库中更改现有数据库表时,此命令通常需要为新约束指定显式名称,以及能够指定要删除或修改的现有约束的名称。

可以使用Constraint.name参数和索引的Index.name参数明确地为约束命名。然而,在约束的情况下,此参数是可选的。还有使用Column.uniqueColumn.index参数的用例,这些参数会创建未指定显式名称的UniqueConstraintIndex对象。

通过架构迁移工具,如Alembic,可以处理现有表格和约束的更改用例。但是,目前既不是  Alembic 也不是 SQLAlchemy  创建约束对象的名称,除非另有规定,否则导致能够更改现有约束的情况,这意味着必须逆向工程关系数据库用于自动分配名称的命名系统,或者必须小心确保所有约束都有名称。

与不得不为所有ConstraintIndex对象分配显式名称相比,可以使用事件构建自动命名方案。这种方法的优点是,约束将获得一致的命名方案,无需在代码中的所有位置都使用显式名称参数,而且约定也会对由Column.uniqueColumn.index参数生成的约束和索引同样适用。从 SQLAlchemy 0.9.2 开始,包含了这种基于事件的方法,可以使用参数MetaData.naming_convention进行配置。

为 MetaData 集合配置命名约定

MetaData.naming_convention 指的是一个字典,接受 Index 类或单独的 Constraint 类作为键,并接受 Python 字符串模板作为值。它还接受一系列字符串代码作为替代键,分别为外键、主键、索引、检查和唯一约束的 "fk""pk""ix""ck""uq"。在这个字典中的字符串模板在与此 MetaData 对象关联的约束或索引没有给出现有名称时使用(包括一个例外情况,即可以进一步装饰现有名称的情况)。

适用于基本情况的一个示例命名约定如下:

convention = {
    "ix": "ix_%(column_0_label)s",
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    "ck": "ck_%(table_name)s_%(constraint_name)s",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s",
}
metadata_obj = MetaData(naming_convention=convention)

上述约定将为目标 MetaData 集合中的所有约束建立名称。例如,当我们创建一个未命名的 UniqueConstraint 时,我们可以观察到生成的名称:

>>> user_table = Table(
...     "user",
...     metadata_obj,
...     Column("id", Integer, primary_key=True),
...     Column("name", String(30), nullable=False),
...     UniqueConstraint("name"),
... )
>>> list(user_table.constraints)[1].name
'uq_user_name'

即使只是使用 Column.unique 标志,这个相同的特性也会生效:

>>> user_table = Table(
...     "user",
...     metadata_obj,
...     Column("id", Integer, primary_key=True),
...     Column("name", String(30), nullable=False, unique=True),
... )
>>> list(user_table.constraints)[1].name
'uq_user_name'

命名约定方法的一个关键优势是名称在 Python 构造时建立,而不是在 DDL 发射时建立。使用 Alembic 的 --autogenerate 功能时的影响是,当生成新的迁移脚本时,命名约定将是明确的:

def upgrade():
    op.create_unique_constraint("uq_user_name", "user", ["name"])

上面的 "uq_user_name" 字符串是从我们的元数据中 --autogenerate 定位到的 UniqueConstraint 对象中复制的。

可用的标记包括 %(table_name)s%(referred_table_name)s%(column_0_name)s%(column_0_label)s%(column_0_key)s%(referred_column_0_name)s,以及每个的多列版本,包括 %(column_0N_name)s%(column_0_N_name)s%(referred_column_0_N_name)s,它们以带有或不带有下划线的形式呈现所有列名称。关于 MetaData.naming_convention 的文档对每个约定有进一步的详细信息。

默认命名规则

MetaData.naming_convention的默认值处理了 SQLAlchemy 长期以来的行为,即为使用Column.index参数创建的Index对象分配名称:

>>> from sqlalchemy.sql.schema import DEFAULT_NAMING_CONVENTION
>>> DEFAULT_NAMING_CONVENTION
immutabledict({'ix': 'ix_%(column_0_label)s'})

长名称的截断

当一个生成的名称,特别是那些使用多列令牌的名称,超出了目标数据库的标识符长度限制时(例如,PostgreSQL 的限制为 63  个字符),名称将使用基于长名称的 md5 哈希的 4 字符后缀进行确定性截断。例如,以下命名约定将基于正在使用的列名称生成非常长的名称:

metadata_obj = MetaData(
    naming_convention={"uq": "uq_%(table_name)s_%(column_0_N_name)s"}
)
long_names = Table(
    "long_names",
    metadata_obj,
    Column("information_channel_code", Integer, key="a"),
    Column("billing_convention_name", Integer, key="b"),
    Column("product_identifier", Integer, key="c"),
    UniqueConstraint("a", "b", "c"),
)

在 PostgreSQL 方言上,长度超过 63 个字符的名称将被截断,如下例所示:

CREATE  TABLE  long_names  (
  information_channel_code  INTEGER,
  billing_convention_name  INTEGER,
  product_identifier  INTEGER,
  CONSTRAINT  uq_long_names_information_channel_code_billing_conventi_a79e
  UNIQUE  (information_channel_code,  billing_convention_name,  product_identifier)
)

上述后缀a79e是基于长名称的 md5 哈希值,并且每次都会生成相同的值,以便为给定模式生成一致的名称。

创建用于命名约定的自定义令牌

还可以通过在 naming_convention 字典中指定一个额外的令牌和一个可调用对象来添加新的令牌。例如,如果我们想要使用 GUID 方案来命名我们的外键约束,我们可以这样做:

import uuid
def fk_guid(constraint, table):
    str_tokens = (
        [
            table.name,
        ]
        + [element.parent.name for element in constraint.elements]
        + [element.target_fullname for element in constraint.elements]
    )
    guid = uuid.uuid5(uuid.NAMESPACE_OID, "_".join(str_tokens).encode("ascii"))
    return str(guid)
convention = {
    "fk_guid": fk_guid,
    "ix": "ix_%(column_0_label)s",
    "fk": "fk_%(fk_guid)s",
}

上面,当我们创建一个新的ForeignKeyConstraint时,我们将获得如下名称:

>>> metadata_obj = MetaData(naming_convention=convention)
>>> user_table = Table(
...     "user",
...     metadata_obj,
...     Column("id", Integer, primary_key=True),
...     Column("version", Integer, primary_key=True),
...     Column("data", String(30)),
... )
>>> address_table = Table(
...     "address",
...     metadata_obj,
...     Column("id", Integer, primary_key=True),
...     Column("user_id", Integer),
...     Column("user_version_id", Integer),
... )
>>> fk = ForeignKeyConstraint(["user_id", "user_version_id"], ["user.id", "user.version"])
>>> address_table.append_constraint(fk)
>>> fk.name
fk_0cd51ab5-8d70-56e8-a83c-86661737766d

另请参阅

MetaData.naming_convention - 用于额外的用法详情以及所有可用命名组件的列表。

命名约束的重要性 - 在 Alembic 文档中。

版本 1.3.0 中的新功能:添加了多列命名令牌,如%(column_0_N_name)s。生成的名称如果超出目标数据库的字符限制将被确定性截断。

命名 CHECK 约束

CheckConstraint对象配置为针对任意 SQL 表达式,该表达式可以有任意数量的列,并且通常使用原始 SQL 字符串进行配置。因此,我们通常使用的与CheckConstraint配合使用的约定是,我们期望对象已经有一个名称,然后我们使用其他约定元素增强它。一个典型的约定是"ck_%(table_name)s_%(constraint_name)s"

metadata_obj = MetaData(
    naming_convention={"ck": "ck_%(table_name)s_%(constraint_name)s"}
)
Table(
    "foo",
    metadata_obj,
    Column("value", Integer),
    CheckConstraint("value > 5", name="value_gt_5"),
)

上述表将生成名称ck_foo_value_gt_5

CREATE  TABLE  foo  (
  value  INTEGER,
  CONSTRAINT  ck_foo_value_gt_5  CHECK  (value  >  5)
)

CheckConstraint 还支持%(columns_0_name)s令牌;我们可以通过确保在约束的表达式中使用Columncolumn()元素来使用此令牌,无论是通过将约束声明为表的一部分:

metadata_obj = MetaData(naming_convention={"ck": "ck_%(table_name)s_%(column_0_name)s"})
foo = Table("foo", metadata_obj, Column("value", Integer))
CheckConstraint(foo.c.value > 5)

或者通过使用column()内联:

from sqlalchemy import column
metadata_obj = MetaData(naming_convention={"ck": "ck_%(table_name)s_%(column_0_name)s"})
foo = Table(
    "foo", metadata_obj, Column("value", Integer), CheckConstraint(column("value") > 5)
)

两者都将生成名称ck_foo_value

CREATE  TABLE  foo  (
  value  INTEGER,
  CONSTRAINT  ck_foo_value  CHECK  (value  >  5)
)

对“列零”的名称确定是通过扫描给定表达式以查找列对象进行的。如果表达式中存在多个列,则扫描会使用确定性搜索,但是表达式的结构将确定哪一列被指定为“列零”。 ### 对布尔、枚举和其他模式类型进行命名配置

SchemaType 类引用诸如 BooleanEnum 之类的类型对象,这些对象生成伴随类型的 CHECK 约束。此处约束的名称最直接通过发送“name”参数设置,例如 Boolean.name

Table("foo", metadata_obj, Column("flag", Boolean(name="ck_foo_flag")))

命名约定功能也可以与这些类型结合使用,通常是通过使用包含%(constraint_name)s的约定,然后将名称应用于类型:

metadata_obj = MetaData(
    naming_convention={"ck": "ck_%(table_name)s_%(constraint_name)s"}
)
Table("foo", metadata_obj, Column("flag", Boolean(name="flag_bool")))

上述表将产生约束名称ck_foo_flag_bool

CREATE  TABLE  foo  (
  flag  BOOL,
  CONSTRAINT  ck_foo_flag_bool  CHECK  (flag  IN  (0,  1))
)

SchemaType 类使用特殊的内部符号,以便命名约定仅在 DDL 编译时确定。在 PostgreSQL 上,有一个原生的 BOOLEAN 类型,因此不需要 Boolean 的 CHECK 约束;我们可以安全地设置 Boolean 类型而不需要名称,即使对于检查约束已经设置了命名约定。如果我们在没有原生 BOOLEAN 类型的数据库上运行,例如 SQLite 或 MySQL,则仅会查阅此约定以获取 CHECK 约束。

CHECK 约束也可以使用column_0_name令牌,与SchemaType非常匹配,因为这些约束只有一列:

metadata_obj = MetaData(naming_convention={"ck": "ck_%(table_name)s_%(column_0_name)s"})
Table("foo", metadata_obj, Column("flag", Boolean()))

上述模式将产生

CREATE  TABLE  foo  (
  flag  BOOL,
  CONSTRAINT  ck_foo_flag  CHECK  (flag  IN  (0,  1))
)

使用 ORM 声明性混合物时使用命名约定

当使用命名约定功能与 ORM 声明式 Mixins 时,每个实际表映射子类必须存在单独的约束对象。有关背景和示例,请参阅使用命名约定在 Mixins 上创建索引和约束的部分。

配置 MetaData 集合的命名约定

MetaData.naming_convention指的是一个字典,接受Index类或个别Constraint类作为键,以及 Python 字符串模板作为值。它还接受一系列字符串代码作为替代键,分别为外键、主键、索引、检查和唯一约束的 "fk""pk""ix""ck""uq"。这个字典中的字符串模板在与这个MetaData对象相关联的约束或索引没有给出现有名称时使用(包括一个现有名称可以进一步修饰的例外情况)。

适用于基本情况的示例命名约定如下:

convention = {
    "ix": "ix_%(column_0_label)s",
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    "ck": "ck_%(table_name)s_%(constraint_name)s",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s",
}
metadata_obj = MetaData(naming_convention=convention)

上述约定将为目标MetaData集合中的所有约束建立名称。例如,当我们创建一个未命名的UniqueConstraint时,我们可以观察到生成的名称。

>>> user_table = Table(
...     "user",
...     metadata_obj,
...     Column("id", Integer, primary_key=True),
...     Column("name", String(30), nullable=False),
...     UniqueConstraint("name"),
... )
>>> list(user_table.constraints)[1].name
'uq_user_name'

即使我们只是使用Column.unique标志,同样的特性也会生效:

>>> user_table = Table(
...     "user",
...     metadata_obj,
...     Column("id", Integer, primary_key=True),
...     Column("name", String(30), nullable=False, unique=True),
... )
>>> list(user_table.constraints)[1].name
'uq_user_name'

命名约定方法的一个关键优势是名称在 Python 构造时建立,而不是在 DDL 发射时。当使用 Alembic 的 --autogenerate 特性时,这个效果是命名约定在生成新的迁移脚本时将是明确的:

def upgrade():
    op.create_unique_constraint("uq_user_name", "user", ["name"])

上述"uq_user_name"字符串是从我们的元数据中--autogenerate定位到的UniqueConstraint对象中复制的。

可用的标记包括%(table_name)s%(referred_table_name)s%(column_0_name)s%(column_0_label)s%(column_0_key)s%(referred_column_0_name)s,以及每个的多列版本,包括%(column_0N_name)s%(column_0_N_name)s%(referred_column_0_N_name)s,它们用下划线分隔或不分隔所有列名。有关MetaData.naming_convention的文档还详细介绍了每个约定。

默认命名约定

MetaData.naming_convention的默认值处理了 SQLAlchemy 的长期行为,即为使用Column.index参数创建的Index对象分配名称:

>>> from sqlalchemy.sql.schema import DEFAULT_NAMING_CONVENTION
>>> DEFAULT_NAMING_CONVENTION
immutabledict({'ix': 'ix_%(column_0_label)s'})

截断长名称

当生成的名称特别是那些使用多列令牌的名称,超出目标数据库的标识符长度限制时(例如,PostgreSQL 的限制为 63  个字符),名称将使用基于长名称的 md5 哈希的 4 字符后缀进行确定性截断。例如,给定以下命名约定,根据使用的列名称,将生成非常长的名称:

metadata_obj = MetaData(
    naming_convention={"uq": "uq_%(table_name)s_%(column_0_N_name)s"}
)
long_names = Table(
    "long_names",
    metadata_obj,
    Column("information_channel_code", Integer, key="a"),
    Column("billing_convention_name", Integer, key="b"),
    Column("product_identifier", Integer, key="c"),
    UniqueConstraint("a", "b", "c"),
)

在 PostgreSQL 方言中,名称长度超过 63 个字符的将被截断,如以下示例所示:

CREATE  TABLE  long_names  (
  information_channel_code  INTEGER,
  billing_convention_name  INTEGER,
  product_identifier  INTEGER,
  CONSTRAINT  uq_long_names_information_channel_code_billing_conventi_a79e
  UNIQUE  (information_channel_code,  billing_convention_name,  product_identifier)
)

上述后缀a79e是基于长名称的 md5 哈希值,并且每次生成相同的值,以便为给定模式生成一致的名称。

创建自定义令牌以用于命名约定

也可以通过在 naming_convention 字典中指定额外的令牌和可调用对象来添加新令牌。例如,如果我们想要使用 GUID 方案为外键约束命名,我们可以这样做:

import uuid
def fk_guid(constraint, table):
    str_tokens = (
        [
            table.name,
        ]
        + [element.parent.name for element in constraint.elements]
        + [element.target_fullname for element in constraint.elements]
    )
    guid = uuid.uuid5(uuid.NAMESPACE_OID, "_".join(str_tokens).encode("ascii"))
    return str(guid)
convention = {
    "fk_guid": fk_guid,
    "ix": "ix_%(column_0_label)s",
    "fk": "fk_%(fk_guid)s",
}

上面,当我们创建一个新的ForeignKeyConstraint时,我们将得到以下名称:

>>> metadata_obj = MetaData(naming_convention=convention)
>>> user_table = Table(
...     "user",
...     metadata_obj,
...     Column("id", Integer, primary_key=True),
...     Column("version", Integer, primary_key=True),
...     Column("data", String(30)),
... )
>>> address_table = Table(
...     "address",
...     metadata_obj,
...     Column("id", Integer, primary_key=True),
...     Column("user_id", Integer),
...     Column("user_version_id", Integer),
... )
>>> fk = ForeignKeyConstraint(["user_id", "user_version_id"], ["user.id", "user.version"])
>>> address_table.append_constraint(fk)
>>> fk.name
fk_0cd51ab5-8d70-56e8-a83c-86661737766d

另请参阅

MetaData.naming_convention - 有关更多使用详细信息以及所有可用命名组件的列表。

命名约束的重要性 - Alembic 文档中的内容。

从版本 1.3.0 开始:添加了多列命名令牌,例如%(column_0_N_name)s。生成的名称如果超过目标数据库的字符限制,将被确定性地截断。

命名 CHECK 约束

CheckConstraint对象针对任意 SQL 表达式进行配置,该表达式可以有任意数量的列,而且通常使用原始 SQL 字符串进行配置。因此,与CheckConstraint一起使用的一种常见约定是,我们期望对象已经具有名称,然后我们使用其他约定元素来增强它。一个典型的约定是"ck_%(table_name)s_%(constraint_name)s"

metadata_obj = MetaData(
    naming_convention={"ck": "ck_%(table_name)s_%(constraint_name)s"}
)
Table(
    "foo",
    metadata_obj,
    Column("value", Integer),
    CheckConstraint("value > 5", name="value_gt_5"),
)

上述表将生成名称ck_foo_value_gt_5

CREATE  TABLE  foo  (
  value  INTEGER,
  CONSTRAINT  ck_foo_value_gt_5  CHECK  (value  >  5)
)

CheckConstraint还支持%(columns_0_name)s令牌;我们可以通过确保在约束表达式中使用Columncolumn()元素来利用这一点,无论是通过单独声明约束还是通过在表内:

metadata_obj = MetaData(naming_convention={"ck": "ck_%(table_name)s_%(column_0_name)s"})
foo = Table("foo", metadata_obj, Column("value", Integer))
CheckConstraint(foo.c.value > 5)

或通过使用column()内联:

from sqlalchemy import column
metadata_obj = MetaData(naming_convention={"ck": "ck_%(table_name)s_%(column_0_name)s"})
foo = Table(
    "foo", metadata_obj, Column("value", Integer), CheckConstraint(column("value") > 5)
)

两者都会生成名称为ck_foo_value的内容:

CREATE  TABLE  foo  (
  value  INTEGER,
  CONSTRAINT  ck_foo_value  CHECK  (value  >  5)
)

“列零”的名称确定是通过扫描给定表达式中的列对象执行的。如果表达式中存在多个列,则扫描将使用确定性搜索,但表达式的结构将确定哪一列被标记为“列零”。

针对布尔型、枚举型和其他模式类型进行命名配置

SchemaType类引用诸如BooleanEnum之类的类型对象,这些对象生成伴随类型的 CHECK 约束。此处约束的名称最直接通过发送“name”参数设置,例如Boolean.name

Table("foo", metadata_obj, Column("flag", Boolean(name="ck_foo_flag")))

命名约定功能也可以与这些类型结合使用,通常是使用包含%(constraint_name)s的约定,然后将名称应用于类型:

metadata_obj = MetaData(
    naming_convention={"ck": "ck_%(table_name)s_%(constraint_name)s"}
)
Table("foo", metadata_obj, Column("flag", Boolean(name="flag_bool")))

上述表将产生约束名为ck_foo_flag_bool

CREATE  TABLE  foo  (
  flag  BOOL,
  CONSTRAINT  ck_foo_flag_bool  CHECK  (flag  IN  (0,  1))
)

SchemaType类使用特殊的内部符号,以便命名约定仅在 DDL 编译时确定。在 PostgreSQL 上,有一种原生的 BOOLEAN 类型,因此不需要Boolean的 CHECK 约束;即使有检查约定,我们也可以安全地设置一个不带名称的Boolean类型。只有在没有原生 BOOLEAN 类型的数据库(如 SQLite 或 MySQL)上运行时,才会咨询此约定以进行 CHECK 约束。

CHECK 约束也可以使用column_0_name令牌,这与SchemaType非常匹配,因为这些约束只有一个列:

metadata_obj = MetaData(naming_convention={"ck": "ck_%(table_name)s_%(column_0_name)s"})
Table("foo", metadata_obj, Column("flag", Boolean()))

上述模式将产生:

CREATE  TABLE  foo  (
  flag  BOOL,
  CONSTRAINT  ck_foo_flag  CHECK  (flag  IN  (0,  1))
)

使用 ORM 声明性Mixin 配置命名约定

在使用命名约定功能与 ORM 声明性混合时,每个实际表映射的子类必须存在单独的约束对象。有关背景和示例,请参见使用命名约定在混合上创建索引和约束部分。


SqlAlchemy 2.0 中文文档(四十)(5)https://developer.aliyun.com/article/1563004

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