Загрузка иерархий наследования¶
Когда классы отображаются в иерархии наследования с использованием стилей «объединенного», «единого» или «конкретного» табличного наследования, как описано в Сопоставление иерархий наследования классов, обычное поведение заключается в том, что запрос к определенному базовому классу будет также выдавать объекты, соответствующие подклассам. Когда один запрос способен возвращать результат с разными классами или подклассами в каждой строке результата, мы используем термин «полиморфная загрузка».
В сфере полиморфной загрузки, в частности, при наследовании объединенных и одиночных таблиц, возникает дополнительная проблема, связанная с тем, какие атрибуты подкласса должны запрашиваться заранее, а какие - позже. Если атрибут определенного подкласса запрашивается заранее, мы можем использовать его в нашем запросе как что-то для фильтрации, и он также будет загружен, когда мы получим наши объекты обратно. Если атрибут не запрашивается заранее, он будет загружен позже, когда нам впервые понадобится доступ к нему. Базовый контроль этого поведения обеспечивается с помощью функции 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