Определение ограничений и индексов

В этом разделе мы обсудим SQL constraints и индексы. В SQLAlchemy ключевые классы включают ForeignKeyConstraint и Index.

Определение внешних ключей

Внешний ключ* в 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_id, которое также существует в столбце user таблицы user_id.

Аргументом ForeignKey чаще всего является строка вида <tablename>.<columnname>, или для таблицы в удаленной схеме или «владельца» вида <schemaname>.<tablename>.<columnname>. Это также может быть фактический объект Column, доступ к которому, как мы увидим позже, осуществляется из существующего объекта Table через его коллекцию c:

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_item с составным внешним ключом, ссылающимся на invoice:

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). Поэтому методы будут разбивать ограничения в таком цикле на отдельные операторы 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"
    ),
)

Когда мы обращаемся к MetaData.create_all() на бэкенде, таком как бэкенд PostgreSQL, цикл между этими двумя таблицами разрешается, и ограничения создаются отдельно:

>>> 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, когда они используются в сочетании с операцией drop, требуют, чтобы ограничение было названо, иначе будет выдана ошибка, подобная следующей:

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

Изменено в версии 1.0.0: - The DDL system invoked by MetaData.create_all() and MetaData.drop_all() will now automatically resolve mutually dependent foreign keys between tables declared by ForeignKeyConstraint and ForeignKey objects, without the need to explicitly set the ForeignKeyConstraint.use_alter flag.

Изменено в версии 1.0.0: - The ForeignKeyConstraint.use_alter flag can be used with an un-named constraint; only the DROP operation will emit a specific error when actually called upon.

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",
    ),
)

Обратите внимание, что эти пункты требуют InnoDB таблиц при использовании с MySQL. Они также могут не поддерживаться в других базах данных.

Ограничение UNIQUE

Уникальные ограничения могут быть созданы анонимно для одного столбца с помощью ключевого слова unique на Column. Явно именованные уникальные ограничения и/или ограничения с несколькими столбцами создаются с помощью конструкции 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.

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"),
)

sqlmytable.create(engine)

PRIMARY KEY Constraint

Ограничение первичного ключа любого объекта Table присутствует неявно, на основе объектов Column, которые отмечены флагом Column.primary_key. Объект 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 Core, позволяющая определить метаданные таблицы, которые, помимо прочего, могут быть использованы 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.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" была скопирована из объекта UniqueConstraint, который --autogenerate находится в наших метаданных.

Доступные маркеры включают %(table_name)s, %(referred_table_name)s, %(column_0_name)s, %(column_0_label)s, %(column_0_key)s, %(referred_column_0_name)s и %(constraint_name)s, а также многоколоночные версии каждого из них, включая %(column_0N_name)s, %(column_0_N_name)s, %(referred_column_0_N_name)s, которые отображают все имена колонок, разделенные подчеркиванием или без него. Более подробная информация о каждом из этих соглашений содержится в документации к MetaData.naming_convention.

Соглашение об именовании по умолчанию

Значение по умолчанию для MetaData.naming_convention обрабатывает давно известное поведение SQLAlchemy по присвоению имени объекту Index, который создается с помощью параметра Column.index:

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

Усечение длинных имен

Если генерируемое имя, особенно те, которые используют маркеры нескольких столбцов, слишком длинное для ограничения длины идентификатора целевой базы данных (например, PostgreSQL имеет ограничение в 63 символа), имя будет детерминистически усечено с помощью 4-символьного суффикса, основанного на md5-хэше длинного имени. Например, приведенное ниже соглашение об именовании будет генерировать очень длинные имена, учитывая используемые имена столбцов:

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 - для получения дополнительной информации об использовании, а также списка всех доступных компонентов именования.

The Importance of Naming Constraints - в документации 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() inline:

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)
)

Определение имени «нулевого столбца» выполняется путем сканирования заданного выражения на наличие объектов столбцов. Если в выражении присутствует более одного столбца, сканирование использует детерминированный поиск, однако структура выражения определяет, какой столбец будет отмечен как «нулевой столбец».

Добавлено в версии 1.0.0: Объект CheckConstraint теперь поддерживает маркер соглашения об именовании column_0_name.

Настройка именования для типов Boolean, Enum и других схем

Класс 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, поэтому ограничение CHECK для Boolean не нужно; мы можем спокойно установить тип Boolean без имени, несмотря на то, что для ограничений проверки существует соглашение об именовании. Это соглашение будет использоваться для ограничения CHECK только в том случае, если мы работаем с базой данных, не имеющей собственного типа BOOLEAN, например SQLite или MySQL.

Ограничение 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))
)

Изменено в версии 1.0: Соглашения об именовании ограничений, не включающие %(constraint_name)s, снова работают с ограничениями SchemaType.

API ограничений

Индексы

Индексы могут быть созданы анонимно (с использованием автоматически генерируемого имени ix_<column label>) для одного столбца с помощью ключевого слова inline index на Column, которое также изменяет использование unique для применения уникальности к самому индексу, вместо добавления отдельного ограничения UNIQUE. Для индексов с определенными именами или охватывающих более одного столбца, используйте конструкцию Index, которая требует указания имени.

Ниже показана таблица Table с несколькими связанными с ней объектами Index. DDL для «CREATE INDEX» выдается сразу после операторов создания таблицы:

metadata_obj = MetaData()
mytable = Table(
    "mytable",
    metadata_obj,
    # an indexed column, with index "ix_mytable_col1"
    Column("col1", Integer, index=True),
    # a uniquely indexed column with index "ix_mytable_col2"
    Column("col2", Integer, index=True, unique=True),
    Column("col3", Integer),
    Column("col4", Integer),
    Column("col5", Integer),
    Column("col6", Integer),
)

# place an index on col3, col4
Index("idx_col34", mytable.c.col3, mytable.c.col4)

# place a unique index on col5, col6
Index("myindex", mytable.c.col5, mytable.c.col6, unique=True)

sqlmytable.create(engine)

Обратите внимание, что в приведенном примере конструкция Index создается вне таблицы, которой она соответствует, используя непосредственно объекты Column. Index также поддерживает определение «inline» внутри Table, используя строковые имена для идентификации столбцов:

metadata_obj = MetaData()
mytable = Table(
    "mytable",
    metadata_obj,
    Column("col1", Integer),
    Column("col2", Integer),
    Column("col3", Integer),
    Column("col4", Integer),
    # place an index on col1, col2
    Index("idx_col12", "col1", "col2"),
    # place a unique index on col3, col4
    Index("idx_col34", "col3", "col4", unique=True),
)

Объект Index также поддерживает свой собственный метод create():

i = Index("someindex", mytable.c.col5)
sqli.create(engine)

Функциональные индексы

Index поддерживает SQL и функциональные выражения, поддерживаемые целевым бэкендом. Для создания индекса по столбцу, использующему убывающее значение, можно использовать модификатор ColumnElement.desc():

from sqlalchemy import Index

Index("someindex", mytable.c.somecol.desc())

Или с бэкендом, поддерживающим функциональные индексы, таким как PostgreSQL, индекс «без учета регистра» может быть создан с помощью функции lower():

from sqlalchemy import func, Index

Index("someindex", func.lower(mytable.c.somecol))

Индексный API

Вернуться на верх