Загрузка иерархий наследования

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

В сфере полиморфной загрузки, в частности, при наследовании объединенных и одиночных таблиц, возникает дополнительная проблема, связанная с тем, какие атрибуты подкласса должны запрашиваться заранее, а какие - позже. Если атрибут определенного подкласса запрашивается заранее, мы можем использовать его в нашем запросе как что-то для фильтрации, и он также будет загружен, когда мы получим наши объекты обратно. Если атрибут не запрашивается заранее, он будет загружен позже, когда нам впервые понадобится доступ к нему. Базовый контроль этого поведения обеспечивается с помощью функции with_polymorphic(), а также двух вариантов, конфигурации маппера mapper.with_polymorphic в сочетании с опцией mapper.polymorphic_load и метода Query -level Query.with_polymorphic(). Семейство «with_polymorphic» предоставляет возможность указать, какие конкретные подклассы определенного базового класса должны быть включены в запрос, что подразумевает, какие столбцы и таблицы будут доступны в SELECT.

Использование with_polymorphic

В следующих разделах мы будем рассматривать примеры Employee / Engineer / Manager, приведенные в Сопоставление иерархий наследования классов.

Обычно, когда Query указывает базовый класс иерархии наследования, запрашиваются только столбцы, локальные для этого базового класса:

session.query(Employee).all()

Выше, как для одиночного, так и для объединенного наследования таблиц, в SELECT будут присутствовать только столбцы, локальные для Employee. Мы можем получить обратно экземпляры Engineer или Manager, однако у них не будут загружены дополнительные атрибуты, пока мы не обратимся к ним впервые, и тогда будет выдана ленивая загрузка.

Аналогично, если бы мы хотели сослаться на столбцы, отображенные на Engineer или Manager в нашем запросе к Employee, эти столбцы недоступны напрямую ни в случае наследования по одной, ни по объединенной таблице, поскольку сущность Employee не ссылается на эти столбцы (обратите внимание, что для наследования по одной таблице это обычное явление, если используется Declarative, но не для классического отображения).

Чтобы решить обе эти проблемы, функция with_polymorphic() предоставляет специальный объект AliasedClass, который представляет диапазон столбцов между подклассами. Этот объект можно использовать в Query, как и любой другой псевдоним. При запросе он представляет все столбцы, присутствующие в указанных классах:

from sqlalchemy.orm import with_polymorphic

eng_plus_manager = with_polymorphic(Employee, [Engineer, Manager])

query = session.query(eng_plus_manager)

Если бы приведенное выше отображение использовало наследование объединенных таблиц, оператор SELECT для этого был бы следующим:

query.all()
SELECT employee.id AS employee_id, engineer.id AS engineer_id, manager.id AS manager_id, employee.name AS employee_name, employee.type AS employee_type, engineer.engineer_info AS engineer_engineer_info, manager.manager_data AS manager_manager_data FROM employee LEFT OUTER JOIN engineer ON employee.id = engineer.id LEFT OUTER JOIN manager ON employee.id = manager.id []

Там, где указано выше, включены дополнительные таблицы / столбцы для «инженера» и «менеджера». Аналогичное поведение происходит в случае наследования одной таблицы.

with_polymorphic() принимает один класс или маппер, список классов/мапперов или строку '*' для указания всех подклассов:

# include columns for Engineer
entity = with_polymorphic(Employee, Engineer)

# include columns for Engineer, Manager
entity = with_polymorphic(Employee, [Engineer, Manager])

# include columns for all mapped subclasses
entity = with_polymorphic(Employee, "*")

Совет

Важно отметить, что with_polymorphic() влияет только на столбцы, которые включены в получаемые строки, а не на типы возвращаемых объектов. Вызов with_polymorphic(Employee, [Manager]) будет ссылаться на строки, содержащие все типы объектов Employee, включая не только объекты Manager, но и объекты Engineer, поскольку они являются подклассами Employee, а также экземпляры Employee, если они присутствуют в базе данных. Эффект от использования with_polymorphic(Employee, [Manager]) будет заключаться только в том, что дополнительные столбцы, специфичные для Manager, будут загружаться в строки результатов, и, как описано ниже в Ссылка на конкретные атрибуты подкласса, также будут доступны для использования в пункте WHERE оператора SELECT.

Использование сглаживания с помощью функции with_polymorphic

Функция with_polymorphic() также обеспечивает «алиасинг» полиморфного selectable, то есть две разные сущности with_polymorphic(), относящиеся к одной иерархии классов, могут быть использованы вместе. Это доступно с помощью флага with_polymorphic.aliased. Для полиморфного выбираемого объекта, относящегося к нескольким таблицам, по умолчанию используется подзапрос. Ниже мы создадим запрос, который будет выбирать для «сотрудник или менеджер» в паре с «сотрудник или инженер» для сотрудников с одинаковыми именами:

engineer_employee = with_polymorphic(Employee, [Engineer], aliased=True)
manager_employee = with_polymorphic(Employee, [Manager], aliased=True)

q = s.query(engineer_employee, manager_employee).join(
    manager_employee,
    and_(
        engineer_employee.id > manager_employee.id,
        engineer_employee.name == manager_employee.name,
    ),
)
q.all()
SELECT anon_1.employee_id AS anon_1_employee_id, anon_1.employee_name AS anon_1_employee_name, anon_1.employee_type AS anon_1_employee_type, anon_1.engineer_id AS anon_1_engineer_id, anon_1.engineer_engineer_name AS anon_1_engineer_engineer_name, anon_2.employee_id AS anon_2_employee_id, anon_2.employee_name AS anon_2_employee_name, anon_2.employee_type AS anon_2_employee_type, anon_2.manager_id AS anon_2_manager_id, anon_2.manager_manager_name AS anon_2_manager_manager_name FROM ( SELECT employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type, engineer.id AS engineer_id, engineer.engineer_name AS engineer_engineer_name FROM employee LEFT OUTER JOIN engineer ON employee.id = engineer.id ) AS anon_1 JOIN ( SELECT employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type, manager.id AS manager_id, manager.manager_name AS manager_manager_name FROM employee LEFT OUTER JOIN manager ON employee.id = manager.id ) AS anon_2 ON anon_1.employee_id > anon_2.employee_id AND anon_1.employee_name = anon_2.employee_name

Приведенное выше создание подзапросов очень многословно. Хотя он создает наилучшую инкапсуляцию двух отдельных запросов, он может быть неэффективным. with_polymorphic() включает дополнительный флаг для помощи в этой ситуации, with_polymorphic.flat, который «сглаживает» комбинацию подзапросов/соединений в прямые соединения, применяя вместо этого псевдонимы к отдельным таблицам. Установка with_polymorphic.flat подразумевает with_polymorphic.aliased, поэтому необходим только один флаг:

engineer_employee = with_polymorphic(Employee, [Engineer], flat=True)
manager_employee = with_polymorphic(Employee, [Manager], flat=True)

q = s.query(engineer_employee, manager_employee).join(
    manager_employee,
    and_(
        engineer_employee.id > manager_employee.id,
        engineer_employee.name == manager_employee.name,
    ),
)
q.all()
SELECT employee_1.id AS employee_1_id, employee_1.name AS employee_1_name, employee_1.type AS employee_1_type, engineer_1.id AS engineer_1_id, engineer_1.engineer_name AS engineer_1_engineer_name, employee_2.id AS employee_2_id, employee_2.name AS employee_2_name, employee_2.type AS employee_2_type, manager_1.id AS manager_1_id, manager_1.manager_name AS manager_1_manager_name FROM employee AS employee_1 LEFT OUTER JOIN engineer AS engineer_1 ON employee_1.id = engineer_1.id JOIN ( employee AS employee_2 LEFT OUTER JOIN manager AS manager_1 ON employee_2.id = manager_1.id ) ON employee_1.id > employee_2.id AND employee_1.name = employee_2.name

Обратите внимание, что при использовании with_polymorphic.flat в сочетании с наследованием объединенных таблиц часто получается право-вложенный JOIN в нашем операторе. Некоторые старые базы данных, в частности старые версии SQLite, могут иметь проблемы с этим синтаксисом, хотя практически все современные версии баз данных теперь поддерживают этот синтаксис.

Примечание

Флаг with_polymorphic.flat применяется только при использовании with_polymorphic с соединенным наследованием таблиц и когда аргумент with_polymorphic.selectable не используется.

Ссылка на конкретные атрибуты подкласса

Сущность, возвращаемая with_polymorphic(), является объектом AliasedClass, который можно использовать в Query как любой другой псевдоним, включая именованные атрибуты для атрибутов класса Employee. В нашем предыдущем примере eng_plus_manager становится объектом, который мы используем для ссылки на трехстороннее внешнее соединение, описанное выше. Она также включает пространства имен для каждого класса, названного в списке классов, так что атрибуты, специфичные для этих подклассов, также могут быть вызваны. Следующий пример иллюстрирует обращение к атрибутам, специфичным для Engineer, а также Manager в терминах eng_plus_manager:

eng_plus_manager = with_polymorphic(Employee, [Engineer, Manager])
query = session.query(eng_plus_manager).filter(
    or_(
        eng_plus_manager.Engineer.engineer_info == "x",
        eng_plus_manager.Manager.manager_data == "y",
    )
)

Запрос, приведенный выше, создаст SQL, похожий на следующий:

query.all()
SELECT employee.id AS employee_id, engineer.id AS engineer_id, manager.id AS manager_id, employee.name AS employee_name, employee.type AS employee_type, engineer.engineer_info AS engineer_engineer_info, manager.manager_data AS manager_manager_data FROM employee LEFT OUTER JOIN engineer ON employee.id = engineer.id LEFT OUTER JOIN manager ON employee.id = manager.id WHERE engineer.engineer_info=? OR manager.manager_data=? ['x', 'y']

Установка параметра with_polymorphic во время конфигурации маппера

Функция with_polymorphic() служит для обеспечения «нетерпеливой» загрузки атрибутов из таблиц подклассов, а также возможности ссылаться на атрибуты из таблиц подклассов во время запроса. Исторически сложилось так, что «нетерпеливая загрузка» столбцов была более важной частью уравнения. Так же как нетерпеливая загрузка для отношений может быть задана в качестве конфигурационной опции, параметр конфигурации mapper.with_polymorphic позволяет сущности использовать полиморфную загрузку по умолчанию. Мы можем добавить этот параметр к нашему отображению Employee, впервые представленному в Наследование объединенных таблиц:

class Employee(Base):
    __tablename__ = "employee"
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    type = Column(String(50))

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": type,
        "with_polymorphic": "*",
    }

Выше приведена обычная настройка для mapper.with_polymorphic, которая заключается в указании звездочки для загрузки всех столбцов подкласса. В случае наследования объединенных таблиц этот параметр следует использовать редко, поскольку он подразумевает, что отображение всегда будет выдавать (часто большую) серию LEFT OUTER JOIN ко многим таблицам, что неэффективно с точки зрения SQL. Для наследования одной таблицы указание звездочки часто является хорошей идеей, поскольку нагрузка все равно будет осуществляться только на одну таблицу, но при этом будет предотвращена дополнительная ленивая нагрузка столбцов, отображенных на подклассы.

Использование with_polymorphic() или Query.with_polymorphic() отменяет настройку уровня маппера mapper.with_polymorphic.

Опция mapper.with_polymorphic также принимает список классов, как и with_polymorphic() для полиморфной загрузки среди подмножества классов. Однако при использовании Declarative предоставление классов в этот список напрямую невозможно, поскольку подклассы, которые мы хотели бы добавить, еще не доступны. Вместо этого мы можем указать каждому подклассу, что он должен участвовать в полиморфной загрузке по умолчанию, используя параметр mapper.polymorphic_load:

class Engineer(Employee):
    __tablename__ = "engineer"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    engineer_info = Column(String(50))
    __mapper_args__ = {"polymorphic_identity": "engineer", "polymorphic_load": "inline"}


class Manager(Employee):
    __tablename__ = "manager"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    manager_data = Column(String(50))
    __mapper_args__ = {"polymorphic_identity": "manager", "polymorphic_load": "inline"}

Установка параметра mapper.polymorphic_load в значение "inline" означает, что приведенные выше классы Engineer и Manager по умолчанию являются частью «полиморфной нагрузки» базового класса Employee, точно так же, как если бы они были добавлены к списку классов mapper.with_polymorphic.

Установка параметра with_polymorphic в запросе

Функция with_polymorphic() развилась из метода уровня запроса Query.with_polymorphic(). Этот метод имеет то же назначение, что и with_polymorphic(), но не так гибок в своих шаблонах использования, поскольку применяется только к первой сущности Query. Затем он вступает в силу для всех вхождений этой сущности, так что на сущность (и ее подклассы) можно ссылаться напрямую, а не использовать объект псевдонима. Для простых случаев можно считать более лаконичным:

session.query(Employee).with_polymorphic([Engineer, Manager]).filter(
    or_(Engineer.engineer_info == "w", Manager.manager_data == "q")
)

Метод Query.with_polymorphic() имеет более сложную задачу, чем функция with_polymorphic(), поскольку он должен правильно преобразовывать сущности типа Engineer и Manager, но не мешать другим сущностям. Если его гибкости не хватает, переключитесь на использование with_polymorphic().

Загрузка полиморфного селектина

Альтернативой использованию семейства функций with_polymorphic() для «нетерпеливой» загрузки дополнительных подклассов на отображении наследования, в основном при использовании наследования объединенных таблиц, является использование полиморфной загрузки «selectin». Это функция ускоренной загрузки, которая работает аналогично функции Выберите загрузку IN загрузки отношений. Учитывая наш пример отображения, мы можем указать загрузке Employee выдавать дополнительный SELECT для каждого подкласса, используя опцию загрузчика selectin_polymorphic():

from sqlalchemy.orm import selectin_polymorphic

query = session.query(Employee).options(
    selectin_polymorphic(Employee, [Manager, Engineer])
)

При выполнении приведенного выше запроса будут выданы два дополнительных оператора SELECT:

query.all() SELECT employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type FROM employee () SELECT engineer.id AS engineer_id, employee.id AS employee_id, employee.type AS employee_type, engineer.engineer_name AS engineer_engineer_name FROM employee JOIN engineer ON employee.id = engineer.id WHERE employee.id IN (?, ?) ORDER BY employee.id (1, 2) SELECT manager.id AS manager_id, employee.id AS employee_id, employee.type AS employee_type, manager.manager_name AS manager_manager_name FROM employee JOIN manager ON employee.id = manager.id WHERE employee.id IN (?) ORDER BY employee.id (3,)

Мы можем аналогичным образом установить вышеуказанный стиль загрузки по умолчанию, указав параметр mapper.polymorphic_load, используя значение "selectin" на основе каждого подкласса:

class Employee(Base):
    __tablename__ = "employee"
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    type = Column(String(50))

    __mapper_args__ = {"polymorphic_identity": "employee", "polymorphic_on": type}


class Engineer(Employee):
    __tablename__ = "engineer"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    engineer_name = Column(String(30))

    __mapper_args__ = {
        "polymorphic_load": "selectin",
        "polymorphic_identity": "engineer",
    }


class Manager(Employee):
    __tablename__ = "manager"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    manager_name = Column(String(30))

    __mapper_args__ = {
        "polymorphic_load": "selectin",
        "polymorphic_identity": "manager",
    }

В отличие от использования with_polymorphic(), при использовании стиля загрузки selectin_polymorphic() у нас нет возможности ссылаться на объекты Engineer или Manager в нашем основном запросе в качестве фильтра, порядка по или других критериев, поскольку эти объекты не присутствуют в исходном запросе, который используется для поиска результатов. Однако мы можем применить опции загрузчика, которые применяются к Engineer или Manager, которые вступят в силу, когда будет выдан вторичный SELECT. Ниже мы предположим, что Manager имеет дополнительное отношение Manager.paperwork, которое мы также хотим нетерпеливо загрузить. Мы можем использовать любой тип ускоренной загрузки, например, объединенную ускоренную загрузку с помощью функции joinedload():

from sqlalchemy.orm import joinedload
from sqlalchemy.orm import selectin_polymorphic

query = session.query(Employee).options(
    selectin_polymorphic(Employee, [Manager, Engineer]), joinedload(Manager.paperwork)
)

Используя приведенный выше запрос, мы получим три оператора SELECT, однако один из них будет иметь значение Manager:

SELECT
    manager.id AS manager_id,
    employee.id AS employee_id,
    employee.type AS employee_type,
    manager.manager_name AS manager_manager_name,
    paperwork_1.id AS paperwork_1_id,
    paperwork_1.manager_id AS paperwork_1_manager_id,
    paperwork_1.data AS paperwork_1_data
FROM employee JOIN manager ON employee.id = manager.id
LEFT OUTER JOIN paperwork AS paperwork_1
ON manager.id = paperwork_1.manager_id
WHERE employee.id IN (?) ORDER BY employee.id
(3,)

Обратите внимание, что полиморфная загрузка selectin имеет те же предостережения, что и загрузка отношений selectin; для сущностей, использующих составной первичный ключ, используемая база данных должна поддерживать кортежи с «IN», в настоящее время известно, что они работают с MySQL и PostgreSQL.

Добавлено в версии 1.2.

Предупреждение

Функция полиморфной загрузки селектина должна рассматриваться как экспериментальная в ранних выпусках серии 1.2.

Комбинация селектина и with_polymorphic

Примечание

работает с версии 1.2.0b3

При тщательном планировании загрузка селектинов может быть применена к иерархии, которая сама использует «with_polymorphic». Особым случаем является использование selectin load для загрузки подтаблицы с объединенным наследованием, которая затем использует «with_polymorphic» для обращения к дальнейшим подклассам, которые могут быть с объединенным или однотабличным наследованием. Если бы мы добавили класс VicePresident, который расширяет Manager, используя однотабличное наследование, мы могли бы гарантировать, что загрузка Manager также полностью загружает подтипы VicePresident одновременно:

# use "Employee" example from the enclosing section


class Manager(Employee):
    __tablename__ = "manager"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    manager_name = Column(String(30))

    __mapper_args__ = {
        "polymorphic_load": "selectin",
        "polymorphic_identity": "manager",
    }


class VicePresident(Manager):
    vp_info = Column(String(30))

    __mapper_args__ = {"polymorphic_load": "inline", "polymorphic_identity": "vp"}

Выше мы добавили в таблицу vp_info столбец manager, локальный для подкласса VicePresident. Этот подкласс связан с полиморфным тождеством "vp", которое ссылается на строки, содержащие эти данные. Установка стиля загрузки на «inline» означает, что загрузка объектов Manager также обеспечит запрос столбца vp_info в том же операторе SELECT. Запрос к Employee, в котором встречается строка Manager, будет выглядеть примерно следующим образом:

SELECT employee.id AS employee_id, employee.name AS employee_name,
       employee.type AS employee_type
FROM employee
)

SELECT manager.id AS manager_id, employee.id AS employee_id,
       employee.type AS employee_type,
       manager.manager_name AS manager_manager_name,
       manager.vp_info AS manager_vp_info
FROM employee JOIN manager ON employee.id = manager.id
WHERE employee.id IN (?) ORDER BY employee.id
(1,)

Комбинирование полиморфной загрузки «selectin» с использованием with_polymorphic() во время запроса также возможно (хотя это очень космические вещи!); если предположить, что вышеприведенные отображения не имели установленных polymorphic_load, мы могли бы получить тот же результат следующим образом:

from sqlalchemy.orm import with_polymorphic, selectin_polymorphic

manager_poly = with_polymorphic(Manager, [VicePresident])

s.query(Employee).options(selectin_polymorphic(Employee, [manager_poly])).all()

Обращение к конкретным подтипам отношений

Атрибуты отображения, соответствующие relationship(), используются в запросах для обозначения связи между двумя отображениями. Обычно это используется для ссылки на relationship() в Query.join(), а также в опциях загрузчика, таких как joinedload(). При использовании relationship(), когда целевой класс является иерархией наследования, API позволяет, чтобы присоединение, нетерпеливая загрузка или другая связь была нацелена на определенный подкласс, псевдокласс или with_polymorphic() псевдоним этой иерархии классов, а не на класс, непосредственно нацеленный relationship().

Метод of_type() позволяет строить объединения по relationship() путям, сужая критерий до конкретных производных псевдонимов или подклассов. Предположим, что таблица employees представляет коллекцию сотрудников, которые связаны с объектом Company. Мы добавим столбец company_id к таблице employees и новую таблицу companies:

class Company(Base):
    __tablename__ = "company"
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    employees = relationship("Employee", backref="company")


class Employee(Base):
    __tablename__ = "employee"
    id = Column(Integer, primary_key=True)
    type = Column(String(20))
    company_id = Column(Integer, ForeignKey("company.id"))
    __mapper_args__ = {
        "polymorphic_on": type,
        "polymorphic_identity": "employee",
    }


class Engineer(Employee):
    __tablename__ = "engineer"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    engineer_info = Column(String(50))
    __mapper_args__ = {"polymorphic_identity": "engineer"}


class Manager(Employee):
    __tablename__ = "manager"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    manager_data = Column(String(50))
    __mapper_args__ = {"polymorphic_identity": "manager"}

При запросе из Company на отношение Employee, метод Query.join(), а также операторы PropComparator.any() и PropComparator.has() создадут объединение от company к employee, не включая engineer или manager в смесь. Если мы хотим иметь критерий, направленный конкретно на класс Engineer, мы можем указать этим методам присоединиться или выполнить подзапрос к набору столбцов, представляющих подкласс, используя оператор PropComparator.of_type():

session.query(Company).join(Company.employees.of_type(Engineer)).filter(
    Engineer.engineer_info == "someinfo"
)

Аналогично, для присоединения из Company к полиморфной сущности, включающей столбцы Engineer и Manager:

manager_and_engineer = with_polymorphic(Employee, [Manager, Engineer])

session.query(Company).join(Company.employees.of_type(manager_and_engineer)).filter(
    or_(
        manager_and_engineer.Engineer.engineer_info == "someinfo",
        manager_and_engineer.Manager.manager_data == "somedata",
    )
)

Операторы PropComparator.any() и PropComparator.has() также могут использоваться с of_type(), например, когда встроенный критерий находится в терминах подкласса:

session.query(Company).filter(
    Company.employees.of_type(Engineer).any(Engineer.engineer_info == "someinfo")
).all()

Стремительная загрузка специфических или полиморфных подтипов

Опции joinedload(), subqueryload(), contains_eager() и другие опции eagerloader поддерживают пути, использующие of_type(). Ниже мы загружаем строки Company, при этом нетерпеливо загружая связанные объекты Engineer, одновременно запрашивая таблицы employee и engineer:

session.query(Company).\
    options(
        subqueryload(Company.employees.of_type(Engineer)).
        subqueryload(Engineer.machines)
        )
    )

Как и в случае с Query.join(), PropComparator.of_type() может использоваться для комбинирования нетерпеливой загрузки и with_polymorphic(), так что все податрибуты всех ссылающихся подтипов могут быть загружены:

manager_and_engineer = with_polymorphic(Employee, [Manager, Engineer], flat=True)

session.query(Company).options(
    joinedload(Company.employees.of_type(manager_and_engineer))
)

Примечание

При использовании with_polymorphic() в сочетании с joinedload(), объект with_polymorphic() должен находиться напротив «aliased» объекта, то есть экземпляра Alias, чтобы полиморфный selectable был aliased (в противном случае выдается информативное сообщение об ошибке).

Типичный способ сделать это - включить флаг with_polymorphic.aliased или flat, который автоматически применит это выравнивание. Однако, если аргумент with_polymorphic.selectable используется для передачи объекта, который уже является объектом Alias, то этот флаг не должен быть установлен. Опция «flat» подразумевает опцию «aliased» и является альтернативной формой алиасинга для объектов join, которая создает меньше подзапросов.

Как только PropComparator.of_type() станет целью нетерпеливой загрузки, именно этот объект мы будем использовать для последующей цепочки, а не исходный класс или производный класс. Если бы мы хотели в дальнейшем нетерпеливо загрузить коллекцию на нетерпеливо загруженный класс Engineer, мы обратились бы к этому классу из пространства имен объекта with_polymorphic():

session.query(Company).\
    options(
        joinedload(Company.employees.of_type(manager_and_engineer)).\
        subqueryload(manager_and_engineer.Engineer.computers)
        )
    )

Загрузка объектов с объединенным наследованием таблиц

При использовании наследования объединенных таблиц, если мы запросим конкретный подкласс, который представляет собой JOIN двух таблиц, как в нашем примере Engineer из раздела наследования, SQL будет выдан как join:

session.query(Engineer).all()

Приведенный выше запрос выдаст SQL вида:

SELECT employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type, engineer.name AS engineer_name FROM employee JOIN engineer ON employee.id = engineer.id

Затем мы получим обратно коллекцию объектов Engineer, которая будет содержать все столбцы из employee и engineer загруженных.

Однако, когда выдается Query против базового класса, поведение заключается в загрузке только из базовой таблицы:

session.query(Employee).all()

Выше, поведение по умолчанию будет заключаться в SELECT только из таблицы employee и не из любых «под» таблиц (engineer и manager, в наших предыдущих примерах):

SELECT employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type FROM employee []

После того, как коллекция объектов Employee была возвращена из запроса, и по мере запроса атрибутов у этих объектов Employee, которые представлены в дочерних таблицах engineer или manager, выполняется вторая загрузка для столбцов в соответствующей строке, если данные еще не были загружены. Таким образом, выше, после доступа к объектам, вы увидите дальнейший SQL, выданный по следующей схеме:

SELECT manager.id AS manager_id, manager.manager_data AS manager_manager_data FROM manager WHERE ? = manager.id [5] SELECT engineer.id AS engineer_id, engineer.engineer_info AS engineer_engineer_info FROM engineer WHERE ? = engineer.id [2]

Функция with_polymorphic() и соответствующие параметры конфигурации позволяют нам вместо этого выдать JOIN, который будет условно загружать employee, engineer или manager, очень похоже на то, как соединенная нетерпеливая загрузка работает для отношений, устраняя необходимость во второй загрузке для каждого объекта:

from sqlalchemy.orm import with_polymorphic

eng_plus_manager = with_polymorphic(Employee, [Engineer, Manager])

query = session.query(eng_plus_manager)

Вышеприведенный запрос создает запрос, который соединяет таблицу employee с таблицами engineer и manager следующим образом:

query.all()
SELECT employee.id AS employee_id, engineer.id AS engineer_id, manager.id AS manager_id, employee.name AS employee_name, employee.type AS employee_type, engineer.engineer_info AS engineer_engineer_info, manager.manager_data AS manager_manager_data FROM employee LEFT OUTER JOIN engineer ON employee.id = engineer.id LEFT OUTER JOIN manager ON employee.id = manager.id []

В разделе Использование with_polymorphic обсуждается функция with_polymorphic() и ее конфигурационные варианты.

Загрузка объектов с наследованием одной таблицы

В современном Declarative отображения с единственным наследованием создают объекты Column, которые отображаются только на подкласс и недоступны из суперкласса, даже если они присутствуют в одной таблице. В нашем примере из Наследование одной таблицы, отображение Manager, например, имело Column, указанное:

class Manager(Employee):
    manager_data = Column(String(50))

    __mapper_args__ = {"polymorphic_identity": "manager"}

Выше не было атрибута Employee.manager_data, хотя таблица employee имеет столбец manager_data. Запрос к Manager будет включать этот столбец в запрос, а также предложение IN для ограничения строк только объектами Manager:

session.query(Manager).all()
SELECT employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type, employee.manager_data AS employee_manager_data FROM employee WHERE employee.type IN (?) ('manager',)

Однако, аналогично наследованию объединенных таблиц, запрос к Employee будет запрашивать только столбцы, сопоставленные с Employee:

session.query(Employee).all()
SELECT employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type FROM employee

Если мы получаем обратно экземпляр Manager из нашего результата, обращение к дополнительным столбцам, сопоставленным только с Manager, вызывает ленивую загрузку для этих столбцов, аналогично объединенному наследованию:

SELECT employee.manager_data AS employee_manager_data
FROM employee
WHERE employee.id = ? AND employee.type IN (?)

Функция with_polymorphic() выполняет ту же роль, что и объединенное наследование в случае одиночного наследования; она позволяет как загружать атрибуты подклассов, так и указывать подклассы в запросе, только без накладных расходов на использование OUTER JOIN:

employee_poly = with_polymorphic(Employee, "*")

q = session.query(employee_poly).filter(
    or_(employee_poly.name == "a", employee_poly.Manager.manager_data == "b")
)

Выше, наш запрос остается к одной таблице, однако мы можем ссылаться на столбцы, присутствующие в Manager или Engineer, используя пространство имен «polymorphic». Поскольку мы указали "*" для сущностей, оба Engineer и Manager будут загружены сразу. SQL будет выглядеть следующим образом:

q.all()
SELECT employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type, employee.manager_data AS employee_manager_data, employee.engineer_info AS employee_engineer_info FROM employee WHERE employee.name = :name_1 OR employee.manager_data = :manager_data_1

Наследование API загрузки

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