Writing SELECT statements for Inheritance Mappings¶
About this Document
このセクションでは、 Mapping Class Inheritance Hierarchies で説明されている ORM Inheritance 機能を使用して設定されたORMマッピングを使用します。 Joined Table Inheritance は最も複雑なORMクエリのケースなので、これに重点を置きます。
SELECTing from the base class vs. specific sub-classes¶
結合された継承階層内のクラスに対して構築されたSELECT文は、クラスがマップされているテーブル、および存在するスーパーテーブルに対して、JOINを使用してそれらをリンクします。次に、クエリは、要求されたタイプのオブジェクトと、要求されたタイプのサブタイプを返します。各行の discriminator 値を使用して正しいタイプを決定します。次のクエリは、 Employee
の Manager
サブクラスに対して確立され、タイプ Manager
のオブジェクトのみを含む結果を返します:
>>> from sqlalchemy import select
>>> stmt = select(Manager).order_by(Manager.id)
>>> managers = session.scalars(stmt).all()
BEGIN (implicit)
SELECT manager.id, employee.id AS id_1, employee.name, employee.type, employee.company_id, manager.manager_name
FROM employee JOIN manager ON employee.id = manager.id ORDER BY manager.id
[...] ()
>>> print(managers)
[Manager('Mr. Krabs')]
SELECT文が階層内の基本クラスに対して行われる場合、デフォルトの動作では、そのクラスのテーブルのみがレンダリングされたSQLに含まれ、JOINは使用されません。すべての場合と同様に、 discriminator 列は異なる要求されたサブタイプを区別するために使用され、その結果、あらゆる可能なサブタイプのオブジェクトが返されます。返されるオブジェクトには、読み込まれたベーステーブルに対応する属性があり、サブテーブルに対応する属性はロードされていない状態で開始され、アクセス時に自動的にロードされます。サブ属性のロードは、このセクションの後半で説明するように、さまざまな方法でより「積極的」になるように構成できます。
以下の例では、 Employee
スーパークラスに対してクエリを作成します。これは、 Manager
、 Engineer
、 Employee
など、あらゆるタイプのオブジェクトが結果セットに含まれる可能性があることを示しています。:
>>> from sqlalchemy import select
>>> stmt = select(Employee).order_by(Employee.id)
>>> objects = session.scalars(stmt).all()
BEGIN (implicit)
SELECT employee.id, employee.name, employee.type, employee.company_id
FROM employee ORDER BY employee.id
[...] ()
>>> print(objects)
[Manager('Mr. Krabs'), Engineer('SpongeBob'), Engineer('Squidward')]
上記では、 Manager
と Engineer
の追加テーブルはSELECTに含まれていませんでした。つまり、返されるオブジェクトには、これらのテーブルから表されるデータがまだ含まれていません。この例では、 Manager
クラスの .manager_name
属性と Engineer
クラスの .engineer_info
属性です。これらの属性は expired 状態から始まり、 lazy loading を使って最初にアクセスされたときに自動的に設定されます:
>>> mr_krabs = objects[0]
>>> print(mr_krabs.manager_name)
SELECT manager.manager_name AS manager_manager_name
FROM manager
WHERE ? = manager.id
[...] (1,)
Eugene H. Krabs
この遅延ロードの振る舞いは、大量のオブジェクトがロードされていて、消費側のアプリケーションがサブクラス固有の属性にアクセスする必要がある場合には望ましくありません。これは、行ごとに追加のSQLを生成する N plus one 問題の例になります。この追加のSQLはパフォーマンスに影響を与える可能性があり、 asyncio を使用するようなアプローチとは互換性がありません。さらに、 Employee
オブジェクトのクエリでは、クエリはベーステーブルのみを対象としているため、 Manager
や Engineer
などのサブクラス固有の属性を含むSQL条件を追加する方法がありませんでした。次の2つのセクションでは、これら2つの問題を異なる方法で解決する2つの構文、 selectin_polymorphic()
ローダオプションと with_polymorphic()
エンティティ構文について詳しく説明します。
Using selectin_polymorphic()¶
サブクラスの属性にアクセスする際のパフォーマンスの問題に対処するために、 selectin_polymorphic()
ローダ戦略を使用して、一度に多くのオブジェクトにわたってこれらの追加属性を:term:熱心にロード`することができます。このローダオプションは :func:`_orm.selectinload 関係ローダ戦略と同様の方法で動作し、階層にロードされたオブジェクトの各サブテーブルに対して追加のSELECT文を発行し、 IN
を使用して主キーに基づいて追加の行を照会します。
selectinload()
は引数として、問い合わせの対象となる基本エンティティと、その後に続く、そのエンティティのサブクラスのシーケンスを受け取ります。そのサブクラスの特定の属性が入力行にロードされます:
>>> from sqlalchemy.orm import selectin_polymorphic
>>> loader_opt = selectin_polymorphic(Employee, [Manager, Engineer])
次に、 selectin_polymorphic()
構文がローダオプションとして使用され、 Select
の Select.options()
メソッドに渡されます。この例では、 selectin_polymorphic()
を使用して、 Manager
と Engineer
の両方のサブクラスにローカルな列を熱心にロードする方法を示しています。
>>> from sqlalchemy.orm import selectin_polymorphic
>>> loader_opt = selectin_polymorphic(Employee, [Manager, Engineer])
>>> stmt = select(Employee).order_by(Employee.id).options(loader_opt)
>>> objects = session.scalars(stmt).all()
{execsql}BEGIN (implicit)
SELECT employee.id, employee.name, employee.type, employee.company_id
FROM employee ORDER BY employee.id
[...] ()
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
[...] (1,)
SELECT engineer.id AS engineer_id, employee.id AS employee_id,
employee.type AS employee_type, engineer.engineer_info AS engineer_engineer_info
FROM employee JOIN engineer ON employee.id = engineer.id
WHERE employee.id IN (?, ?) ORDER BY employee.id
[...] (2, 3)
{stop}>>> print(objects)
[Manager('Mr. Krabs'), Engineer('SpongeBob'), Engineer('Squidward')]
上の例では、 Engineer.engineer_info
や Manager.manager_name
などの追加の属性を取得するために、2つの追加のSELECT文が発行されています。これで、追加のSQL文が発行されることなく、ロードされたオブジェクトのこれらのサブ属性にアクセスできるようになりました。:
>>> print(objects[0].manager_name)
Eugene H. Krabs
Tip
selectin_polymorphic()
ローダオプションは、ベースの employee
テーブルが2番目の2つの”eager load”問い合わせに含まれる必要がないという事実に対してまだ最適化されていません。したがって、上の例では、 employee
からの列がすでにロードされていても、 employee
から manager
と engineer
へのJOINを見ることができます。これは、この点に関してより洗練されていて、必要でないときにJOINを除外できる selectinload()
関係戦略とは対照的です。
Applying selectin_polymorphic() to an existing eager load¶
selectin_polymorphic()
が文によってロードされるトップレベルのエンティティのオプションとして指定されることに加えて、 selectin_polymorphic()
を既存のロードのターゲットに指定することもできます。 setup のマッピングには、親の Company
エンティティと、 Employee
エンティティを参照する Company.employees
relationship()
が含まれているので、次のように Load.selectin_polymorphic()
をチェーンローダオプションとして適用することで、すべての Employee
オブジェクトとそのサブタイプのすべての属性を熱心にロードする`Company`エンティティに対するSELECTを示すことができます。この形式では、最初の引数は前のローダオプション(この場合は selectinload()
)から暗黙的に指定されるので、ロードしたい追加のターゲットサブクラスだけを指定します:
>>> from sqlalchemy.orm import selectinload
>>> stmt = select(Company).options(
... selectinload(Company.employees).selectin_polymorphic([Manager, Engineer])
... )
>>> for company in session.scalars(stmt):
... print(f"company: {company.name}")
... print(f"employees: {company.employees}")
BEGIN (implicit)
SELECT company.id, company.name
FROM company
[...] ()
SELECT employee.company_id AS employee_company_id, employee.id AS employee_id,
employee.name AS employee_name, employee.type AS employee_type
FROM employee
WHERE employee.company_id IN (?)
[...] (1,)
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
[...] (1,)
SELECT engineer.id AS engineer_id, employee.id AS employee_id,
employee.type AS employee_type,
engineer.engineer_info AS engineer_engineer_info
FROM employee JOIN engineer ON employee.id = engineer.id
WHERE employee.id IN (?, ?) ORDER BY employee.id
[...] (2, 3)
company: Krusty Krab
employees: [Manager('Mr. Krabs'), Engineer('SpongeBob'), Engineer('Squidward')]
See also
Eager Loading of Polymorphic Subtypes - 代わりに with_polymorphic()
を使用して、上記と同等の例を示します
Applying loader options to the subclasses loaded by selectin_polymorphic¶
selectin_polymorphic()
が出力するSELECT文はそれ自体がORM文なので、特定のサブクラスを参照する他のローダオプション( Relationship Loading Techniques で文書化されているものなど)を追加することもできます。これらのオプションは、 selectin_polymorphic()
オプションの 兄弟 として、つまり select.options()
内でカンマ区切りで適用する必要があります。
例えば、 Manager
マッパーが Paperwork
と呼ばれるエンティティに対して one to many 関係を持っていると考えた場合、 selectin_polymorphic()
と selectinload()
を組み合わせて、このコレクションをすべての Manager
オブジェクトに積極的にロードすることができます。この場合、 Manager
オブジェクトのサブ属性も積極的にロードされます。
>>> from sqlalchemy.orm import selectin_polymorphic
>>> stmt = (
... select(Employee)
... .order_by(Employee.id)
... .options(
... selectin_polymorphic(Employee, [Manager, Engineer]),
... selectinload(Manager.paperwork),
... )
... )
>>> objects = session.scalars(stmt).all()
{execsql}SELECT employee.id, employee.name, employee.type, employee.company_id
FROM employee ORDER BY employee.id
[...] ()
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
[...] (1,)
SELECT paperwork.manager_id AS paperwork_manager_id, paperwork.id AS paperwork_id, paperwork.document_name AS paperwork_document_name
FROM paperwork
WHERE paperwork.manager_id IN (?)
[...] (1,)
SELECT engineer.id AS engineer_id, employee.id AS employee_id, employee.type AS employee_type, engineer.engineer_info AS engineer_engineer_info
FROM employee JOIN engineer ON employee.id = engineer.id
WHERE employee.id IN (?, ?) ORDER BY employee.id
[...] (2, 3)
{stop}>>> print(objects[0])
Manager('Mr. Krabs')
>>> print(objects[0].paperwork)
[Paperwork('Secret Recipes'), Paperwork('Krabby Patty Orders')]
Applying loader options when selectin_polymorphic is itself a sub-option¶
New in version 2.0.21.
前のセクションでは、 selectin_polymorphic()
と selectinload()
を兄弟オプションとして使用しており、どちらも select.options()
への単一の呼び出し内で使用されています。 Applying selectin_polymorphic() to an existing eager load の例のように、ターゲットエンティティがすでに親関係からロードされているエンティティである場合、 Specifying Sub-Options with Load.options() に示されているように、親にサブオプションを適用する Load.options()
メソッドを使用して、この 兄弟
パターンを適用できます。以下では、この2つの例を組み合わせて、 Company.employees
をロードし、 Manager
クラスと Engineer
クラスの属性もロードし、 Manager.paperwork
属性もロードします。:
>>> from sqlalchemy.orm import selectinload
>>> stmt = select(Company).options(
... selectinload(Company.employees).options(
... selectin_polymorphic(Employee, [Manager, Engineer]),
... selectinload(Manager.paperwork),
... )
... )
>>> for company in session.scalars(stmt):
... print(f"company: {company.name}")
... for employee in company.employees:
... if isinstance(employee, Manager):
... print(f"manager: {employee.name} paperwork: {employee.paperwork}")
BEGIN (implicit)
SELECT company.id, company.name
FROM company
[...] ()
SELECT employee.company_id AS employee_company_id, employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type
FROM employee
WHERE employee.company_id IN (?)
[...] (1,)
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
[...] (1,)
SELECT paperwork.manager_id AS paperwork_manager_id, paperwork.id AS paperwork_id, paperwork.document_name AS paperwork_document_name
FROM paperwork
WHERE paperwork.manager_id IN (?)
[...] (1,)
SELECT engineer.id AS engineer_id, employee.id AS employee_id, employee.type AS employee_type, engineer.engineer_info AS engineer_engineer_info
FROM employee JOIN engineer ON employee.id = engineer.id
WHERE employee.id IN (?, ?) ORDER BY employee.id
[...] (2, 3)
company: Krusty Krab
manager: Mr. Krabs paperwork: [Paperwork('Secret Recipes'), Paperwork('Krabby Patty Orders')]
Configuring selectin_polymorphic() on mappers¶
selectin_polymorphic()
の動作は、 Mapper.polymorphic_load
パラメータを使用して、デフォルトで実行されるように特定のマッパーで設定することができます。この場合、サブクラスごとに selectin
の値を使用します。以下の例は、このパラメータを Engineer
および Manager
サブクラスで使用する方法を示しています。
class Employee(Base):
__tablename__ = "employee"
id = mapped_column(Integer, primary_key=True)
name = mapped_column(String(50))
type = mapped_column(String(50))
__mapper_args__ = {"polymorphic_identity": "employee", "polymorphic_on": type}
class Engineer(Employee):
__tablename__ = "engineer"
id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
engineer_info = mapped_column(String(30))
__mapper_args__ = {
"polymorphic_load": "selectin",
"polymorphic_identity": "engineer",
}
class Manager(Employee):
__tablename__ = "manager"
id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
manager_name = mapped_column(String(30))
__mapper_args__ = {
"polymorphic_load": "selectin",
"polymorphic_identity": "manager",
}
上記のマッピングでは、 Employee
クラスに対するSELECT文は、その文が発行された時に自動的にローダオプションとして selectin_polymorphic(Employee, [Engineer, Manager])
の使用を想定します。
Using with_polymorphic()¶
オブジェクトのロードのみに影響する selectin_polymorphic()
とは対照的に、 with_polymorphic()
構文は、最も一般的には含まれる各サブテーブルへの一連のLEFT OUTER JOINとして、多様構造のSQLクエリのレンダリング方法に影響します。この結合構造は**多様選択可能**として知られています。一度に複数のサブテーブルのビューを提供することで、 with_polymorphic()
は、個々のサブテーブルに基づいてフィルタリング条件を追加する機能とともに、一度に複数の継承されたクラスにわたってSELECT文を作成する手段を提供します。
with_polymorphic()
は本質的には aliased()
構文の特殊な形式です。引数として selectin_polymorphic()
と同様の形式を受け入れます。これは問い合わせの対象となる基本エンティティで、その後にそのエンティティのサブクラスのシーケンスが続き、その特定の属性が入力行にロードされます:
>>> from sqlalchemy.orm import with_polymorphic
>>> employee_poly = with_polymorphic(Employee, [Engineer, Manager])
全てのサブクラスがエンティティの一部であるべきことを示すために、 with_polymorphic()
は、全てのクラスを示すためにクラスの並びの代わりに渡すことができる文字列 "*"
も受け付けます(これは selectin_polymorphic()
ではまだサポートされていないことに注意してください):
>>> employee_poly = with_polymorphic(Employee, "*")
以下の例は、前節で説明したのと同じ操作で、 Manager
と Engineer
のすべての列を一度にロードします。:
>>> stmt = select(employee_poly).order_by(employee_poly.id)
>>> objects = session.scalars(stmt).all()
BEGIN (implicit)
SELECT employee.id, employee.name, employee.type, employee.company_id,
manager.id AS id_1, manager.manager_name, engineer.id AS id_2, engineer.engineer_info
FROM employee
LEFT OUTER JOIN manager ON employee.id = manager.id
LEFT OUTER JOIN engineer ON employee.id = engineer.id ORDER BY employee.id
[...] ()
>>> print(objects)
[Manager('Mr. Krabs'), Engineer('SpongeBob'), Engineer('Squidward')]
selectin_polymorphic()
の場合と同じく、サブクラスの属性はすでにロードされています:
>>> print(objects[0].manager_name)
Eugene H. Krabs
with_polymorphic()
が生成するデフォルトの選択対象はLEFT OUTER JOINを使用するので、データベースの観点から見ると、この問い合わせは selectin_polymorphic()
がテーブルごとに生成されるJOINのみを使用する単純なSELECT文で採用している手法ほど最適化されていません。
Filtering Subclass Attributes with with_polymorphic()¶
with_polymorphic()
構文は、サブクラスへの参照を可能にする名前空間を含めることで、含まれているサブクラスマッパの属性を利用できるようにします。前節で作成した employee_poly
構文には、ポリモーフィックSELECTの観点から Engineer
と Manager
の名前空間を提供する .Engineer
と .Manager
という名前の属性が含まれています。以下の例では、 or_()
構文を使って、両方のクラスに対する条件を同時に作成できます:
>>> from sqlalchemy import or_
>>> employee_poly = with_polymorphic(Employee, [Engineer, Manager])
>>> stmt = (
... select(employee_poly)
... .where(
... or_(
... employee_poly.Manager.manager_name == "Eugene H. Krabs",
... employee_poly.Engineer.engineer_info
... == "Senior Customer Engagement Engineer",
... )
... )
... .order_by(employee_poly.id)
... )
>>> objects = session.scalars(stmt).all()
SELECT employee.id, employee.name, employee.type, employee.company_id, manager.id AS id_1,
manager.manager_name, engineer.id AS id_2, engineer.engineer_info
FROM employee
LEFT OUTER JOIN manager ON employee.id = manager.id
LEFT OUTER JOIN engineer ON employee.id = engineer.id
WHERE manager.manager_name = ? OR engineer.engineer_info = ?
ORDER BY employee.id
[...] ('Eugene H. Krabs', 'Senior Customer Engagement Engineer')
>>> print(objects)
[Manager('Mr. Krabs'), Engineer('Squidward')]
Using aliasing with with_polymorphic¶
with_polymorphic()
構文は、 aliased()
の特殊なケースとして、 aliased()
が行う基本的な機能も提供します。これは、polymorphic selectable自体の”エイリアス”の機能です。具体的には、同じクラス階層を参照する2つ以上の with_polymorphic()
エンティティを1つの文で同時に使用できることを意味します。
結合された継承マッピングでこの機能を使用するには、通常、 with_polymorphic.aliased
と with_polymorphic.flat
の2つのパラメータを渡します。 with_polymorphic.aliased
パラメータは、polymorphic selectableがこの構成体に一意のエイリアス名で参照される必要があることを示します。 with_polymorphic.flat
パラメータは、デフォルトのLEFT OUTER JOIN polymorphic selectableに固有であり、より最適化された形式のエイリアスを文で使用する必要があることを示します。
この機能を説明するために、以下の例では、 Engineer
と結合された Employee
と、 Manager
と結合された Employee
という2つの異なる多相エンティティに対してSELECTを発行します。これらの2つの多相エンティティは、いずれも基本の employee
テーブルを多相選択に含むため、このテーブルを2つの異なるコンテキストで区別するためにエイリアスを適用する必要があります。2つの多相エンティティは2つの個別のテーブルのように扱われるため、一般的には何らかの方法で互いに結合する必要があります。次に示すように、エンティティは company_id
列で結合され、 Employee
/ Manager
エンティティに対していくつかの制限条件が追加されます。:
>>> manager_employee = with_polymorphic(Employee, [Manager], aliased=True, flat=True)
>>> engineer_employee = with_polymorphic(Employee, [Engineer], aliased=True, flat=True)
>>> stmt = (
... select(manager_employee, engineer_employee)
... .join(
... engineer_employee,
... engineer_employee.company_id == manager_employee.company_id,
... )
... .where(
... or_(
... manager_employee.name == "Mr. Krabs",
... manager_employee.Manager.manager_name == "Eugene H. Krabs",
... )
... )
... .order_by(engineer_employee.name, manager_employee.name)
... )
>>> for manager, engineer in session.execute(stmt):
... print(f"{manager} {engineer}")
SELECT
employee_1.id, employee_1.name, employee_1.type, employee_1.company_id,
manager_1.id AS id_1, manager_1.manager_name,
employee_2.id AS id_2, employee_2.name AS name_1, employee_2.type AS type_1,
employee_2.company_id AS company_id_1, engineer_1.id AS id_3, engineer_1.engineer_info
FROM employee AS employee_1
LEFT OUTER JOIN manager AS manager_1 ON employee_1.id = manager_1.id
JOIN
(employee AS employee_2 LEFT OUTER JOIN engineer AS engineer_1 ON employee_2.id = engineer_1.id)
ON employee_2.company_id = employee_1.company_id
WHERE employee_1.name = ? OR manager_1.manager_name = ?
ORDER BY employee_2.name, employee_1.name
[...] ('Mr. Krabs', 'Eugene H. Krabs')
Manager('Mr. Krabs') Manager('Mr. Krabs')
Manager('Mr. Krabs') Engineer('SpongeBob')
Manager('Mr. Krabs') Engineer('Squidward')
上の例では、 with_polymorphic.flat
の振る舞いとして、多様な選択可能なものは個々のテーブルのLEFT OUTER JOINとして残り、それ自体に匿名の別名が与えられます。また、右入れ子のJOINも生成されます。
with_polymorphic.flat
パラメータを省略した場合の通常の振る舞いは、各polymorphic selectableを副問い合わせで囲み、より冗長な形式にすることです:
>>> manager_employee = with_polymorphic(Employee, [Manager], aliased=True)
>>> engineer_employee = with_polymorphic(Employee, [Engineer], aliased=True)
>>> stmt = (
... select(manager_employee, engineer_employee)
... .join(
... engineer_employee,
... engineer_employee.company_id == manager_employee.company_id,
... )
... .where(
... or_(
... manager_employee.name == "Mr. Krabs",
... manager_employee.Manager.manager_name == "Eugene H. Krabs",
... )
... )
... .order_by(engineer_employee.name, manager_employee.name)
... )
>>> print(stmt)
SELECT anon_1.employee_id, anon_1.employee_name, anon_1.employee_type,
anon_1.employee_company_id, anon_1.manager_id, anon_1.manager_manager_name, anon_2.employee_id AS employee_id_1,
anon_2.employee_name AS employee_name_1, anon_2.employee_type AS employee_type_1,
anon_2.employee_company_id AS employee_company_id_1, anon_2.engineer_id, anon_2.engineer_engineer_info
FROM
(SELECT employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type,
employee.company_id AS employee_company_id,
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_1
JOIN
(SELECT employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type,
employee.company_id AS employee_company_id, engineer.id AS engineer_id, engineer.engineer_info AS engineer_engineer_info
FROM employee LEFT OUTER JOIN engineer ON employee.id = engineer.id) AS anon_2
ON anon_2.employee_company_id = anon_1.employee_company_id
WHERE anon_1.employee_name = :employee_name_2 OR anon_1.manager_manager_name = :manager_manager_name_1
ORDER BY anon_2.employee_name, anon_1.employee_name
上記の形式は歴史的に、右入れ子のJOINを必ずしもサポートしていないバックエンドに対してより移植性があります。また、 with_polymorphic()
で使用される”polymorphic selectable”が、 concrete table inheritance マッピングなどのマッピングを使用する場合や、一般的に代替のpolymorphic selectableを使用する場合のように、テーブルの単純なLEFT OUTER JOINでない場合にも適切かもしれません。
Configuring with_polymorphic() on mappers¶
selectin_polymorphic()
の場合と同様に、 with_polymorphic()
構文もマッパー設定バージョンをサポートしています。これは2つの異なる方法で設定できます。 mapper.with_polymorphic
パラメータを使用して基底クラス上で設定する方法と、 Mapper.polymorphic_load
パラメータをサブクラスごとに使用してより現代的な形式で値 "inline"
を渡して設定する方法です。
Warning
結合された継承マッピングでは、このセクションで説明されているマッパーレベルの mapper.with_polymorphic
パラメータを使用するのではなく、クエリ内で with_polymorphic()
を明示的に使用するか、暗黙的なEagerサブクラスのロードでは Mapper.polymorphic_load
を "selectin"
とともに使用してください。このパラメータは、SELECT文内のFROM句を書き直すことを目的とした複雑なヒューリスティックを呼び出します。これは、より複雑な文、特に同じマップされたエンティティを参照するネストされたサブクエリを持つ文の構築を妨げる可能性があります。
例えば、以下のように Mapper.polymorphic_load
を使って inline
として Employee
マッピングを記述することができます。:
class Employee(Base):
__tablename__ = "employee"
id = mapped_column(Integer, primary_key=True)
name = mapped_column(String(50))
type = mapped_column(String(50))
__mapper_args__ = {"polymorphic_identity": "employee", "polymorphic_on": type}
class Engineer(Employee):
__tablename__ = "engineer"
id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
engineer_info = mapped_column(String(30))
__mapper_args__ = {
"polymorphic_load": "inline",
"polymorphic_identity": "engineer",
}
class Manager(Employee):
__tablename__ = "manager"
id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
manager_name = mapped_column(String(30))
__mapper_args__ = {
"polymorphic_load": "inline",
"polymorphic_identity": "manager",
}
上記のマッピングでは、 Employee
クラスに対するSELECT文は、文が発行されたときに自動的に with_polymorphic(Employee, [Engineer, Manager])
をプライマリエンティティとして使用することになります。:
print(select(Employee))
SELECT employee.id, employee.name, employee.type, engineer.id AS id_1,
engineer.engineer_info, manager.id AS id_2, manager.manager_name
FROM employee
LEFT OUTER JOIN engineer ON employee.id = engineer.id
LEFT OUTER JOIN manager ON employee.id = manager.id
マッパーレベルの”with polymorphic”を使用する場合、クエリはサブクラスのエンティティを直接参照することもできます。サブクラスのエンティティは、ポリモーフィッククエリで結合されたテーブルを暗黙的に表します。上記では、デフォルトの Employee
エンティティに対して Manager
と Engineer
を直接参照することができます。:
print(
select(Employee).where(
or_(Manager.manager_name == "x", Engineer.engineer_info == "y")
)
)
SELECT employee.id, employee.name, employee.type, engineer.id AS id_1,
engineer.engineer_info, manager.id AS id_2, manager.manager_name
FROM employee
LEFT OUTER JOIN engineer ON employee.id = engineer.id
LEFT OUTER JOIN manager ON employee.id = manager.id
WHERE manager.manager_name = :manager_name_1
OR engineer.engineer_info = :engineer_info_1
しかし、別のエイリアスされたコンテキストで Employee
エンティティやそのサブエンティティを参照する必要がある場合は、 Using aliasing with with_polymorphic で説明されているように、これらのエイリアスされたエンティティを定義するために、再び with_polymorphic()
を直接使用します。
polymorphic selectableをより中央集権的に制御するために、よりレガシーな形式のマッパーレベルのpolymorphic制御を使用することができます。これは、基底クラスで設定された Mapper.with_polymorphic
パラメータです。このパラメータは with_polymorphic()
構文と同等の引数を受け入れますが、結合された継承マッピングで一般的に使用されるのは、次のようにすべてのサブテーブルがLEFT OUTER JOINEDであることを示す単純なアスタリスクです。
class Employee(Base):
__tablename__ = "employee"
id = mapped_column(Integer, primary_key=True)
name = mapped_column(String(50))
type = mapped_column(String(50))
__mapper_args__ = {
"polymorphic_identity": "employee",
"with_polymorphic": "*",
"polymorphic_on": type,
}
class Engineer(Employee):
__tablename__ = "engineer"
id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
engineer_info = mapped_column(String(30))
__mapper_args__ = {
"polymorphic_identity": "engineer",
}
class Manager(Employee):
__tablename__ = "manager"
id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
manager_name = mapped_column(String(30))
__mapper_args__ = {
"polymorphic_identity": "manager",
}
全体として、 with_polymorphic()
や Mapper.with_polymorphic
などのオプションで使用されるLEFT OUTER JOIN形式は、SQLやデータベースオプティマイザの観点からは扱いにくいかもしれません。結合された継承マッピングにおけるサブクラス属性の一般的なロードには、 selectin_polymorphic()
アプローチ、または Mapper.polymorphic_load
を "selectin"
に設定するのと同等のマッパーレベルのアプローチが好まれ、必要な場合にのみクエリごとに with_polymorphic()
を使用します。
Joining to specific sub-types or with_polymorphic() entities¶
with_polymorphic()
エンティティは aliased()
の特殊なケースなので、特に relationship()
構文をON句として使用する場合、結合のターゲットとして多様エンティティを扱うために、 Using Relationship to join between aliased targets で詳しく説明されている通常のエイリアスと同じテクニックを使用します。最も簡潔には PropComparator.of_type()
を使用します。以下の例では、1対多の関係 Company.employees
に沿った親 Company
エンティティからの結合を説明します。これは、ターゲットとして with_polymorphic()
エンティティを使用して、 Employee
オブジェクトにリンクするように setup で設定されています:
>>> employee_plus_engineer = with_polymorphic(Employee, [Engineer])
>>> stmt = (
... select(Company.name, employee_plus_engineer.name)
... .join(Company.employees.of_type(employee_plus_engineer))
... .where(
... or_(
... employee_plus_engineer.name == "SpongeBob",
... employee_plus_engineer.Engineer.engineer_info
... == "Senior Customer Engagement Engineer",
... )
... )
... )
>>> for company_name, emp_name in session.execute(stmt):
... print(f"{company_name} {emp_name}")
SELECT company.name, employee.name AS name_1
FROM company JOIN (employee LEFT OUTER JOIN engineer ON employee.id = engineer.id) ON company.id = employee.company_id
WHERE employee.name = ? OR engineer.engineer_info = ?
[...] ('SpongeBob', 'Senior Customer Engagement Engineer')
Krusty Krab SpongeBob
Krusty Krab Squidward
より直接的には、 PropComparator.of_type()
は、 relationship()
に沿った結合を relationship()
のターゲットの特定のサブタイプに制限するために、あらゆる種類の継承マッピングとともにも使用されます。上記のクエリは、厳密には次のように Engineer
ターゲットに関して記述できます。
>>> stmt = (
... select(Company.name, Engineer.name)
... .join(Company.employees.of_type(Engineer))
... .where(
... or_(
... Engineer.name == "SpongeBob",
... Engineer.engineer_info == "Senior Customer Engagement Engineer",
... )
... )
... )
>>> for company_name, emp_name in session.execute(stmt):
... print(f"{company_name} {emp_name}")
{execsql}SELECT company.name, employee.name AS name_1
FROM company JOIN (employee JOIN engineer ON employee.id = engineer.id) ON company.id = employee.company_id
WHERE employee.name = ? OR engineer.engineer_info = ?
[...] ('SpongeBob', 'Senior Customer Engagement Engineer')
{stop}Krusty Krab SpongeBob
Krusty Krab Squidward
上で見たように、 with_polymorphic(Employee, [Engineer])
の polymorphic selectable
ではなく、 Engineer
ターゲットに直接結合することには、LEFT OUTER JOINではなく内部JOINを使用するという便利な特性があります。これは一般的に、SQLオプティマイザの観点からはよりパフォーマンスに優れています。
Eager Loading of Polymorphic Subtypes¶
前のセクションの Select.join()
メソッドで説明した PropComparator.of_type()
の使用は、 selectinload()
や joinedload()
などの relationship loader options にも同様に適用できます。
基本的な例として、 Company
オブジェクトをロードし、さらに with_polymorphic()
構文を使って階層全体に対して Company.employees
のすべての要素をロードしたい場合は、次のように記述します。
>>> all_employees = with_polymorphic(Employee, "*")
>>> stmt = select(Company).options(selectinload(Company.employees.of_type(all_employees)))
>>> for company in session.scalars(stmt):
... print(f"company: {company.name}")
... print(f"employees: {company.employees}")
{execsql}SELECT company.id, company.name
FROM company
[...] ()
SELECT employee.company_id AS employee_company_id, 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, engineer.id AS engineer_id,
engineer.engineer_info AS engineer_engineer_info
FROM employee
LEFT OUTER JOIN manager ON employee.id = manager.id
LEFT OUTER JOIN engineer ON employee.id = engineer.id
WHERE employee.company_id IN (?)
[...] (1,)
company: Krusty Krab
employees: [Manager('Mr. Krabs'), Engineer('SpongeBob'), Engineer('Squidward')]
上記の問い合わせは、前節の Applying selectin_polymorphic() to an existing eager load で説明した selectin_polymorphic()
バージョンと直接比較することができます。
See also
Applying selectin_polymorphic() to an existing eager load - 代わりに selectin_polymorphic()
を使用して、上記と同等の例を示します
SELECT Statements for Single Inheritance Mappings¶
Single Table Inheritance Setup
このセクションでは、 Single Table Inheritance で説明されている、単一のテーブルを使用して階層内の複数のクラスを表す単一のテーブルの継承について説明します。
結合された継承マッピングとは対照的に、単一継承マッピングの場合のSELECT文の構築は単純になる傾向があります。これは、すべて単一継承の階層ではテーブルが1つしかないためです。
継承階層がすべて単一継承であるか、結合継承と単一継承が混在しているかにかかわらず、単一継承のSELECT文は、追加のWHERE条件でSELECT文を制限することによって、基本クラスとサブクラスに対するクエリを区別します。
例えば、 Employee
の単一継承マッピングの問い合わせは、テーブルの単純なSELECTを使って、 Manager
、 Engineer
、 Employee
型のオブジェクトをロードします。
>>> stmt = select(Employee).order_by(Employee.id)
>>> for obj in session.scalars(stmt):
... print(f"{obj}")
{execsql}BEGIN (implicit)
SELECT employee.id, employee.name, employee.type
FROM employee ORDER BY employee.id
[...] ()
{stop}Manager('Mr. Krabs')
Engineer('SpongeBob')
Engineer('Squidward')
特定のサブクラスに対してロードが発行されると、行を制限する追加の基準がSELECTに追加されます。たとえば、以下のように、 Engineer
エンティティに対するSELECTが実行されます。:
>>> stmt = select(Engineer).order_by(Engineer.id)
>>> objects = session.scalars(stmt).all()
SELECT employee.id, employee.name, employee.type, employee.engineer_info
FROM employee
WHERE employee.type IN (?) ORDER BY employee.id
[...] ('engineer',)
>>> for obj in objects:
... print(f"{obj}")
Engineer('SpongeBob')
Engineer('Squidward')
Optimizing Attribute Loads for Single Inheritance¶
サブクラスの属性がどのように選択されるかに関する単一継承マッピングのデフォルトの動作は、結合された継承の動作と似ています。サブクラス固有の属性は、デフォルトで2番目のSELECTを発行します。以下の例では、タイプ Manager
の単一の Employee
がロードされますが、要求されたクラスは Employee
であるため、 Manager.manager_name
属性はデフォルトでは存在せず、アクセスされると追加のSELECTが発行されます。
>>> mr_krabs = session.scalars(select(Employee).where(Employee.name == "Mr. Krabs")).one()
{execsql}BEGIN (implicit)
SELECT employee.id, employee.name, employee.type
FROM employee
WHERE employee.name = ?
[...] ('Mr. Krabs',)
{stop}>>> mr_krabs.manager_name
{execsql}SELECT employee.manager_name AS employee_manager_name
FROM employee
WHERE employee.id = ? AND employee.type IN (?)
[...] (1, 'manager')
{stop}'Eugene H. Krabs'
この動作を変更するために、結合された継承のロードで使用されるこれらの追加の属性を積極的にロードするために使用されるのと同じ一般的な概念が、単一継承にも適用されます。これには、 selectin_polymorphic()
オプションと with_polymorphic()
オプションの使用が含まれます。後者は単に追加の列を含み、SQLの観点からは単一継承マッパーの方が効率的です:
>>> employees = with_polymorphic(Employee, "*")
>>> stmt = select(employees).order_by(employees.id)
>>> objects = session.scalars(stmt).all()
BEGIN (implicit)
SELECT employee.id, employee.name, employee.type,
employee.manager_name, employee.engineer_info
FROM employee ORDER BY employee.id
[...] ()
>>> for obj in objects:
... print(f"{obj}")
Manager('Mr. Krabs')
Engineer('SpongeBob')
Engineer('Squidward')
>>> objects[0].manager_name
'Eugene H. Krabs'
通常、単一継承のサブクラスマッピングをロードするオーバーヘッドは最小限であるため、単一継承マッピングには、特定のサブクラス属性のロードが一般的であると予想されるサブクラスに対して、 Mapper.polymorphic_load
パラメータを "inline"
に設定して含めることをお勧めします。このオプションを含めるように変更された setup の例を以下に示します。:
>>> class Base(DeclarativeBase):
... pass
>>> class Employee(Base):
... __tablename__ = "employee"
... id: Mapped[int] = mapped_column(primary_key=True)
... name: Mapped[str]
... type: Mapped[str]
...
... def __repr__(self):
... return f"{self.__class__.__name__}({self.name!r})"
...
... __mapper_args__ = {
... "polymorphic_identity": "employee",
... "polymorphic_on": "type",
... }
>>> class Manager(Employee):
... manager_name: Mapped[str] = mapped_column(nullable=True)
... __mapper_args__ = {
... "polymorphic_identity": "manager",
... "polymorphic_load": "inline",
... }
>>> class Engineer(Employee):
... engineer_info: Mapped[str] = mapped_column(nullable=True)
... __mapper_args__ = {
... "polymorphic_identity": "engineer",
... "polymorphic_load": "inline",
... }
上記のマッピングでは、 Manager
クラスと Engineer
クラスの列は、自動的に Employee
エンティティに対するSELECT文に含まれます。
>>> print(select(Employee))
{printsql}SELECT employee.id, employee.name, employee.type,
employee.manager_name, employee.engineer_info
FROM employee
Inheritance Loading API¶
Object Name | Description |
---|---|
selectin_polymorphic(base_cls, classes) |
Indicate an eager load should take place for all attributes specific to a subclass. |
with_polymorphic(base, classes[, selectable, flat, ...]) |
Produce an |
- function sqlalchemy.orm.with_polymorphic(base: Type[_O] | Mapper[_O], classes: Literal['*'] | Iterable[Type[Any]], selectable: Literal[False, None] | FromClause = False, flat: bool = False, polymorphic_on: ColumnElement[Any] | None = None, aliased: bool = False, innerjoin: bool = False, adapt_on_names: bool = False, name: str | None = None, _use_mapper_path: bool = False) AliasedClass[_O] ¶
Produce an
AliasedClass
construct which specifies columns for descendant mappers of the given base.Using this method will ensure that each descendant mapper’s tables are included in the FROM clause, and will allow filter() criterion to be used against those tables. The resulting instances will also have those columns already loaded so that no “post fetch” of those columns will be required.
See also
Using with_polymorphic() - full discussion of
with_polymorphic()
.- Parameters:
base¶ – Base class to be aliased.
classes¶ – a single class or mapper, or list of class/mappers, which inherit from the base class. Alternatively, it may also be the string
'*'
, in which case all descending mapped classes will be added to the FROM clause.aliased¶ – when True, the selectable will be aliased. For a JOIN, this means the JOIN will be SELECTed from inside of a subquery unless the
with_polymorphic.flat
flag is set to True, which is recommended for simpler use cases.flat¶ – Boolean, will be passed through to the
FromClause.alias()
call so that aliases ofJoin
objects will alias the individual tables inside the join, rather than creating a subquery. This is generally supported by all modern databases with regards to right-nested joins and generally produces more efficient queries. Setting this flag is recommended as long as the resulting SQL is functional.selectable¶ –
a table or subquery that will be used in place of the generated FROM clause. This argument is required if any of the desired classes use concrete table inheritance, since SQLAlchemy currently cannot generate UNIONs among tables automatically. If used, the
selectable
argument must represent the full set of tables and columns mapped by every mapped class. Otherwise, the unaccounted mapped columns will result in their table being appended directly to the FROM clause which will usually lead to incorrect results.When left at its default value of
False
, the polymorphic selectable assigned to the base mapper is used for selecting rows. However, it may also be passed asNone
, which will bypass the configured polymorphic selectable and instead construct an ad-hoc selectable for the target classes given; for joined table inheritance this will be a join that includes all target mappers and their subclasses.polymorphic_on¶ – a column to be used as the “discriminator” column for the given selectable. If not given, the polymorphic_on attribute of the base classes’ mapper will be used, if any. This is useful for mappings that don’t have polymorphic loading behavior by default.
innerjoin¶ – if True, an INNER JOIN will be used. This should only be specified if querying for one specific subtype only
adapt_on_names¶ –
Passes through the
aliased.adapt_on_names
parameter to the aliased object. This may be useful in situations where the given selectable is not directly related to the existing mapped selectable.New in version 1.4.33.
name¶ –
Name given to the generated
AliasedClass
.New in version 2.0.31.
- function sqlalchemy.orm.selectin_polymorphic(base_cls: _EntityType[Any], classes: Iterable[Type[Any]]) _AbstractLoad ¶
Indicate an eager load should take place for all attributes specific to a subclass.
This uses an additional SELECT with IN against all matched primary key values, and is the per-query analogue to the
"selectin"
setting on themapper.polymorphic_load
parameter.New in version 1.2.
See also