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)
使用字符串的优势在于,user
和 user_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_id
和invoice_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_alter
和 ForeignKey.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_alter
和 ForeignKey.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”后面的短语还可以允许其他与正在使用的数据库特定的短语相对应的短语。 ForeignKey
和 ForeignKeyConstraint
对象通过 onupdate
和 ondelete
关键字参数支持通过生成此子句。 值是任何字符串,将在适当的“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_alter
和 ForeignKey.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_alter
和 ForeignKey.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”之后的短语可能还允许其他特定于正在使用的数据库的短语。 ForeignKey
和 ForeignKeyConstraint
对象支持通过 onupdate
和 ondelete
关键字参数生成此子句。该值是任何字符串,将在适当的“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.unique
和Column.index
参数的用例,这些参数会创建未指定显式名称的UniqueConstraint
和Index
对象。
通过架构迁移工具,如Alembic,可以处理现有表格和约束的更改用例。但是,目前既不是 Alembic 也不是 SQLAlchemy 创建约束对象的名称,除非另有规定,否则导致能够更改现有约束的情况,这意味着必须逆向工程关系数据库用于自动分配名称的命名系统,或者必须小心确保所有约束都有名称。
与不得不为所有Constraint
和Index
对象分配显式名称相比,可以使用事件构建自动命名方案。这种方法的优点是,约束将获得一致的命名方案,无需在代码中的所有位置都使用显式名称参数,而且约定也会对由Column.unique
和Column.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
令牌;我们可以通过确保在约束的表达式中使用Column
或column()
元素来使用此令牌,无论是通过将约束声明为表的一部分:
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
类引用诸如 Boolean
和 Enum
之类的类型对象,这些对象生成伴随类型的 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
令牌;我们可以通过确保在约束表达式中使用Column
或column()
元素来利用这一点,无论是通过单独声明约束还是通过在表内:
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
类引用诸如Boolean
和Enum
之类的类型对象,这些对象生成伴随类型的 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