ORM Configuration

How do I map a table that has no primary key?

SQLAlchemy ORMは、特定のテーブルにマップするために、少なくとも1つの列が主キー列として指定されている必要があります。複数列、つまり複合主キーももちろん完全に実現可能です。これらの列は、実際にデータベースに主キー列として認識されている必要は ありません が、認識されていることは良い考えです。列が主キーと同じように 動作する ことだけが必要です。たとえば、行に対して一意でNULLを使用できない識別子として動作することが必要です。

ほとんどのORMでは、メモリ内のオブジェクトはデータベーステーブル内の一意に識別可能な行に対応する必要があるため、オブジェクトにはある種の主キーが定義されている必要があります。少なくとも、これにより、そのオブジェクトの行だけに影響し、他の行には影響しないUPDATE文とDELETE文のターゲットにすることができます。しかし、主キーの重要性はそれをはるかに超えています。SQLAlchemyでは、ORMにマップされたすべてのオブジェクトは、 identity map と呼ばれるパターンを使用して、 Session 内で特定のデータベース行に常に一意にリンクされます。このパターンは、SQLAlchemyで使用される作業システムの単位の中心であり、ORM使用の最も一般的な(そしてあまり一般的ではない)パターンの鍵でもあります。

Note

ここで重要なのは、ここではSQLAlchemy ORMについて話しているだけだということです。SQLAlchemy ORMはCore上に構築され、Table オブジェクトや select() 構文などだけを扱うアプリケーションですが、主キーが存在したり、何らかの形でテーブルに関連付けられたりする必要は ありません (ただし、SQLでは、実際に特定の行を更新したり削除したりする必要がないように、すべてのテーブルは実際に何らかの種類の主キーを持つ必要があります)。

ほとんどの場合、テーブルには candidate key と呼ばれるものがあります。これは、1つの候補キーを一意に識別する1つまたは一連の列です。テーブルに本当にこれがなく、実際に完全に重複した行がある場合、そのテーブルは first normal form に対応していないため、マッピングできません。そうでなければ、最適な候補キーを構成する列をマッパーに直接適用することができます:

class SomeClass(Base):
    __table__ = some_table_with_no_pk
    __mapper_args__ = {
        "primary_key": [some_table_with_no_pk.c.uid, some_table_with_no_pk.c.bar]
    }

さらに良いのは、完全に宣言されたテーブルメタデータを使用する場合、それらの列に対して「primary_key=True」フラグを使用することです。:

class SomeClass(Base):
    __tablename__ = "some_table_with_no_pk"

    uid = Column(Integer, primary_key=True)
    bar = Column(String, primary_key=True)

リレーショナル・データベース内のすべてのテーブルには、主キーが必要です。多対多の関連付けテーブルであっても、主キーは次の2つの関連付け列の複合です。:

CREATE TABLE my_association (
  user_id INTEGER REFERENCES user(id),
  account_id INTEGER REFERENCES account(id),
  PRIMARY KEY (user_id, account_id)
)

How do I configure a Column that is a Python reserved word or similar?

列ベースの属性には、マッピングで必要な任意の名前を付けることができます。 Naming Declarative Mapped Columns Explicitly を参照してください。

How do I get a list of all columns, relationships, mapped attributes, etc. given a mapped class?

この情報はすべて Mapper オブジェクトから取得できます。

特定のマップされたクラスの Mapper を取得するには、そのクラスに対して inspect() 関数を呼び出します:

from sqlalchemy import inspect

mapper = inspect(MyClass)

ここから、次のようなプロパティを使用して、クラスに関するすべての情報にアクセスできます。:

  • Mapper.attrs - マップされたすべての属性の名前空間です。属性自体は MapperProperty のインスタンスで、該当する場合、マップされ たSQL式や列につながる追加の属性が含まれています。

  • Mapper.column_attrs - 列とSQL式の属性に限定された、マップされた属性の名前空間です。 Mapper.columns を使用して Column オブジェクトを直接取得することもできます。

  • Mapper.columns - Column オブジェクトの名前空間と、マッピングに関連付けられた他の名前付きSQL式です。

  • Mapper.local_table - このマッパーにとって”ローカル”な Table です。これは Mapper.mapped_table とは、継承を使ってcomposed selectableにマップされたマッパーの場合は異なります。

I’m getting a warning or error about “Implicitly combining column X under attribute Y”

この条件は、名前のために同じ属性名でマップされている2つの列がマッピングに含まれているが、これが意図的であることを示すものではない場合を指します。マップされたクラスは、独立した値を格納するすべての属性に対して明示的な名前を持つ必要があります。2つの列が同じ名前を持ち、明確にされていない場合、それらは同じ属性に分類され、その結果、最初に属性に割り当てられた列に基づいて、一方の列の値が他方の列に コピー されます。

この動作は多くの場合望ましいものであり、2つの列が継承マッピング内の外部キー関係を介してリンクされている場合に警告なしに許可されます。警告または例外が発生した場合、この問題は、列を異なる名前の属性に割り当てるか、それらを組み合わせる場合は column_property() を使用してこれを明示的に行うことで解決できます。

次に例を示します。:

from sqlalchemy import Integer, Column, ForeignKey
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)

class B(A):
    __tablename__ = "b"

    id = Column(Integer, primary_key=True)
    a_id = Column(Integer, ForeignKey("a.id"))

SQLAlchemyバージョン0.9.5では、上記の条件が検出され、 ABid 列が同じ名前の属性 id の下に結合されていることが警告されます。これは、 B オブジェクトの主キーが常に A オブジェクトの主キーを反映することを意味するため、深刻な問題です。

これを解決するマッピングは次のようになります。:

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)

class B(A):
    __tablename__ = "b"

    b_id = Column("id", Integer, primary_key=True)
    a_id = Column(Integer, ForeignKey("a.id"))

B.a_idA.id と関連しているにもかかわらず、 A.idB.id を互いの鏡にしたいと思ったとします。 column_property() を使ってこれらを組み合わせることができます:

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)

class B(A):
    __tablename__ = "b"

    # probably not what you want, but this is a demonstration
    id = column_property(Column(Integer, primary_key=True), A.id)
    a_id = Column(Integer, ForeignKey("a.id"))

I’m using Declarative and setting primaryjoin/secondaryjoin using an and_() or or_(), and I am getting an error message about foreign keys.

このようにしていますか?:

class MyClass(Base):
    # ....

    foo = relationship(
        "Dest", primaryjoin=and_("MyClass.id==Dest.foo_id", "MyClass.foo==Dest.bar")
    )

これは2つの文字列式の and_() であり、SQLAlchemyはこれに対してマッピングを適用することができません。宣言型では、 relationship() 引数を文字列として指定できます。これは eval() を使って式オブジェクトに変換されます。しかし、これは and_() 式の中では起こりません。宣言型の特殊な操作であり、primaryjoinや他の引数に文字列として渡されたものの*全体*にのみ適用されます:

class MyClass(Base):
    # ....

    foo = relationship(
        "Dest", primaryjoin="and_(MyClass.id==Dest.foo_id, MyClass.foo==Dest.bar)"
    )

または、必要なオブジェクトが既に使用可能な場合は、文字列をスキップします。:

class MyClass(Base):
    # ....

    foo = relationship(
        Dest, primaryjoin=and_(MyClass.id == Dest.foo_id, MyClass.foo == Dest.bar)
    )

同じ考え方が、 foreign_keys のような他の引数にも当てはまります:

# wrong !
foo = relationship(Dest, foreign_keys=["Dest.foo_id", "Dest.bar_id"])

# correct !
foo = relationship(Dest, foreign_keys="[Dest.foo_id, Dest.bar_id]")

# also correct !
foo = relationship(Dest, foreign_keys=[Dest.foo_id, Dest.bar_id])

# if you're using columns from the class that you're inside of, just use the column objects !
class MyClass(Base):
    foo_id = Column(...)
    bar_id = Column(...)
    # ...

    foo = relationship(Dest, foreign_keys=[foo_id, bar_id])

What are default, default_factory and insert_default and what should I use?

PEP-681データクラス変換が追加されたことにより、SQLAlchemyのAPIには多少の衝突があります。これは、その命名規則に厳密です。 Declarative Dataclass Mapping に示されているように、 MappedAsDataclass を使用している場合、PEP-681が機能します。MappedAsDataclassを使用していない場合、これは適用されません。

Part One - Classic SQLAlchemy that is not using dataclasses

MappedAsDataclass を使って いない 場合、SQLAlchemyで長年使われてきたように、 mapped_column() (および Column )構文はパラメータ mapped_column.default をサポートします。これはPython側のデフォルト(データベースのスキーマ定義の一部となるサーバ側のデフォルトとは対照的に)を示し、 INSERT 文が発行されたときに発生します。このデフォルトは、文字列のような 任意の 静的Python値、 または Python呼び出し可能関数、 または SQLAlchemy SQL構文です。 mapped_column.default の完全なドキュメントは Client-Invoked SQL Expressions にあります。

MappedAsDataclass使用しない ORMマッピングで mapped_column.default を使用する場合、このデフォルト値/呼び出し可能な値は、 オブジェクトを最初に構築したときには表示されません 。これは、SQLAlchemyがオブジェクトに対して INSERT 文を作成したときにのみ行われます。

注意すべき非常に重要なことは、 mapped_column() (および Column )を使用する場合、古典的な mapped_column.default

パラメータは mapped_column.insert_default という新しい名前でも利用できます。 mapped_column() を構築していて、 MappedAsDataclass使用していない 場合、 mapped_column.default パラメータと mapped_column.insert_default パラメータは 同義 です。

Part Two - Using Dataclasses support with MappedAsDataclass

MappedAsDataclass 、つまり Declarative Dataclass Mapping で使用される特定の形式のマッピングを 使用している 場合、 mapped_column.default キーワードの意味が変わります。この名前が動作を変えるのは理想的ではないことは認識していますが、PEP-681では mapped_column.default がこの意味を持つことを要求しているので、他に選択肢はありませんでした。

データクラスを使用する場合、 mapped_column.default パラメータは Python Dataclasses に記述されている方法で使用する必要があります。これは文字列や数値のような定数値を参照し、 構築されるとすぐにオブジェクトに適用されます 。また、この時点では、 Columnmapped_column.default パラメータにも適用されます。この場合、オブジェクトに存在しなくても自動的に INSERT 文で使用されます。構築時にオブジェクトに適用されるデータクラスの呼び出し可能オブジェクトを使用する場合は、 mapped_column.default_factory を使用します。

上記のパート1で説明した mapped_column.defaultINSERT のみの動作にアクセスするには、代わりに mapped_column.insert_default パラメータを使用します。データクラスが使用される場合、 mapped_column.insert_default は、パラメータが静的な値または呼び出し可能なコアレベルの”デフォルト”プロセスへの直接ルートであり続けます。

Summary Chart

Construct

Works with dataclasses?

Works without dataclasses?

Accepts scalar?

Accepts callable?

Populates object immediately?

mapped_column.default

Only if no dataclasses

Only if dataclasses

mapped_column.insert_default

mapped_column.default_factory

Only if dataclasses