Сопоставление столбцов таблицы

Вводная информация по отображению на колонки относится к теме конфигурации Table; общая форма относится к одной из трех форм:

Во всех описанных выше случаях конструктор mapper в конечном итоге вызывается с заполненным объектом Table, переданным в качестве выбираемой единицы для отображения. Поведение mapper заключается в том, чтобы собрать все столбцы в отображаемом Table в атрибуты отображаемого объекта, каждый из которых назван в соответствии с именем самого столбца (в частности, атрибут key в Column). Это поведение может быть изменено несколькими способами.

Именование столбцов отличается от имен атрибутов

Сопоставление по умолчанию использует то же имя для Column, что и для сопоставленного атрибута - в частности, оно соответствует атрибуту Column.key на Column, который по умолчанию совпадает с Column.name.

Имя, присвоенное атрибуту Python, который отображается на Column, может отличаться от Column.name или Column.key, просто присвоив его таким образом, как мы иллюстрируем здесь в декларативном отображении:

class User(Base):
    __tablename__ = "user"
    id = Column("user_id", Integer, primary_key=True)
    name = Column("user_name", String(50))

Где выше User.id разрешается в колонку с именем user_id, а User.name разрешается в колонку с именем user_name.

При сопоставлении с существующей таблицей на объект Column можно ссылаться напрямую:

class User(Base):
    __table__ = user_table
    id = user_table.c.user_id
    name = user_table.c.user_name

Соответствующая техника для отображения imperative заключается в том, чтобы поместить нужный ключ в словарь mapper.properties с нужным ключом:

mapper_registry.map_imperatively(
    User,
    user_table,
    properties={
        "id": user_table.c.user_id,
        "name": user_table.c.user_name,
    },
)

Автоматизация схем именования столбцов из отраженных таблиц

В предыдущем разделе Именование столбцов отличается от имен атрибутов мы показали, как Column, явно отображенный на класс, может иметь имя атрибута, отличное от имени столбца. Но что если мы не перечисляем объекты Column явно, а вместо этого автоматизируем создание объектов Table с помощью отражения (т.е. как описано в Отражение объектов базы данных)? В этом случае мы можем использовать событие DDLEvents.column_reflect() для перехвата производства Column объектов и предоставления им Column.key по нашему выбору. Событие проще всего связать с используемым объектом MetaData, например, ниже мы используем событие, связанное с экземпляром declarative_base:

@event.listens_for(Base.metadata, "column_reflect")
def column_reflect(inspector, table, column_info):
    # set column.key = "attr_<lower_case_name>"
    column_info["key"] = "attr_%s" % column_info["name"].lower()

С помощью вышеуказанного события отражение объектов Column будет перехвачено нашим событием, добавляющим новый элемент «.key», например, в отображении, как показано ниже:

class MyClass(Base):
    __table__ = Table("some_table", Base.metadata, autoload_with=some_engine)

Этот подход также работает как с базовым классом DeferredReflection, так и с расширением Automap. Что касается конкретно automap, смотрите раздел automap_intercepting_columns для справки.

См.также

Декларативное отображение с помощью отраженных таблиц

DDLEvents.column_reflect()

automap_intercepting_columns - в документации Automap

Использование свойства column_property для опций уровня столбца

Опции могут быть указаны при отображении Column с помощью функции column_property(). Эта функция явно создает ColumnProperty, используемый mapper() для отслеживания Column; обычно mapper() создает его автоматически. Используя column_property(), мы можем передать дополнительные аргументы о том, как мы хотим, чтобы Column был отображен. Ниже мы передаем опцию active_history, которая указывает, что изменение значения этого столбца должно привести к тому, что сначала будет загружено прежнее значение:

from sqlalchemy.orm import column_property


class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = column_property(Column(String(50)), active_history=True)

column_property() также используется для сопоставления одного атрибута с несколькими столбцами. Этот случай возникает при сопоставлении с join(), который имеет атрибуты, приравненные друг к другу:

class User(Base):
    __table__ = user.join(address)

    # assign "user.id", "address.user_id" to the
    # "id" attribute
    id = column_property(user_table.c.id, address_table.c.user_id)

Другие примеры использования этой функции см. в разделе Сопоставление класса с несколькими таблицами.

Еще одно место, где требуется column_property() - это указание SQL-выражений в качестве сопоставленных атрибутов, как показано ниже, где мы создаем атрибут fullname, который является строковой конкатенацией столбцов firstname и lastname:

class User(Base):
    __tablename__ = "user"
    id = Column(Integer, primary_key=True)
    firstname = Column(String(50))
    lastname = Column(String(50))
    fullname = column_property(firstname + " " + lastname)

Смотрите примеры такого использования в Выражения SQL как сопоставленные атрибуты.

Сопоставление с явным набором столбцов первичного ключа

Конструкция Mapper для успешного отображения таблицы всегда требует, чтобы по крайней мере один столбец был определен как «первичный ключ» для этой выбираемой таблицы. Это необходимо для того, чтобы при загрузке или сохранении объекта ORM его можно было поместить в identity map с соответствующим identity key.

Для поддержки этого варианта использования все объекты FromClause (где FromClause является общей базой для таких объектов, как Table, Join, Subquery и т.д.) имеют атрибут , который возвращает коллекцию тех объектов , которые указывают на то, что они являются частью «первичного ключа». ) имеют атрибут FromClause.primary_key, который возвращает коллекцию тех объектов Column, которые указывают, что они являются частью «первичного ключа», который является производным от каждого объекта Column, являющегося членом коллекции PrimaryKeyConstraint, связанной с Table, от которого они в конечном итоге происходят.

В тех случаях, когда сопоставляемый selectable не включает столбцы, которые явно являются частью ограничения первичного ключа в родительской таблице, необходимо определить набор столбцов первичного ключа, определяемый пользователем. Для этого используется параметр mapper.primary_key.

Учитывая следующий пример отображения Imperative Table на существующий объект Table, как это происходит в сценарии, например, когда Table были reflected из существующей базы данных, где таблица не имеет объявленного первичного ключа, мы можем отобразить такую таблицу, как в следующем примере:

from sqlalchemy import Column
from sqlalchemy import MetaData
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy import UniqueConstraint
from sqlalchemy.orm import declarative_base


metadata = MetaData()
group_users = Table(
    "group_users",
    metadata,
    Column("user_id", String(40), nullable=False),
    Column("group_id", String(40), nullable=False),
    UniqueConstraint("user_id", "group_id"),
)


Base = declarative_base()


class GroupUsers(Base):
    __table__ = group_users
    __mapper_args__ = {"primary_key": [group_users.c.user_id, group_users.c.group_id]}

Выше, таблица group_users представляет собой некую ассоциативную таблицу со строковыми столбцами user_id и group_id, но первичный ключ не установлен; вместо этого есть только UniqueConstraint, устанавливающий, что эти два столбца представляют собой уникальный ключ. Mapper не проверяет автоматически уникальные ограничения для первичных ключей; вместо этого мы используем параметр mapper.primary_key, передавая коллекцию [group_users.c.user_id, group_users.c.group_id], указывающую, что эти два столбца должны быть использованы для построения ключа идентификации для экземпляров класса GroupUsers.

Сопоставление подмножества столбцов таблицы

Иногда объект Table был доступен с помощью процесса отражения, описанного в Отражение объектов базы данных для загрузки структуры таблицы из базы данных. Для такой таблицы, имеющей много столбцов, на которые не нужно ссылаться в приложении, аргументы include_properties или exclude_properties могут указывать, что отображать следует только подмножество столбцов. Например:

class User(Base):
    __table__ = user_table
    __mapper_args__ = {"include_properties": ["user_id", "user_name"]}

…сопоставит класс User с таблицей user_table, включая только столбцы user_id и user_name - на остальные столбцы ссылки нет. Аналогично:

class Address(Base):
    __table__ = address_table
    __mapper_args__ = {"exclude_properties": ["street", "city", "state", "zip"]}

…сопоставит класс Address с таблицей address_table, включая все присутствующие столбцы, кроме street, city, state и zip.

Когда используется это отображение, на столбцы, которые не включены, не будет ссылок ни в каких операторах SELECT, выдаваемых Query, ни в каком-либо сопоставленном атрибуте сопоставленного класса, который представляет столбец; присвоение атрибута с таким именем не будет иметь никакого эффекта, кроме обычного присвоения атрибута в Python.

В некоторых случаях несколько столбцов могут иметь одинаковое имя, например, при сопоставлении с объединением двух или более таблиц, имеющих общее имя столбца. Объекты include_properties и exclude_properties также могут содержать объекты Column для более точного описания того, какие столбцы должны быть включены или исключены:

class UserAddress(Base):
    __table__ = user_table.join(addresses_table)
    __mapper_args__ = {
        "exclude_properties": [address_table.c.id],
        "primary_key": [user_table.c.id],
    }

Примечание

функции вставки и обновления по умолчанию, настроенные на отдельные объекты Column, т.е. те, которые описаны в Колонки INSERT/UPDATE по умолчанию, включая те, которые настроены параметрами Column.default, Column.onupdate, Column.server_default и Column.server_onupdate, будут продолжать нормально функционировать, даже если эти объекты Column не отображены. Это происходит потому, что в случае Column.default и Column.onupdate объект Column все еще присутствует на базовом Table, что позволяет функциям по умолчанию работать, когда ORM выдает INSERT или UPDATE, а в случае Column.server_default и Column.server_onupdate сама реляционная база данных выдает эти параметры по умолчанию как поведение на стороне сервера.

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