Working with Database Metadata¶
エンジンとSQLの実行が停止したので、いくつかの錬金術を始める準備ができました。SQLAlchemy CoreとORMの中心的な要素は、SQLクエリの流暢で構成可能な構築を可能にするSQL式言語です。これらのクエリの基礎は、テーブルや列などのデータベース概念を表すPythonオブジェクトです。これらのオブジェクトは総称して database metadata と呼ばれます。
SQLAlchemyにおけるデータベースメタデータの最も一般的な基本オブジェクトは、 MetaData
、 Table
、および Column
として知られています。以下のセクションでは、これらのオブジェクトがコア指向のスタイルとORM指向のスタイルの両方でどのように使用されるかを説明します。
ORM readers, stay with us!
他のセクションと同様に、コアユーザはORMセクションをスキップできますが、ORMユーザは両方の観点からこれらのオブジェクトに最も精通しているでしょう。ここで説明する Table
オブジェクトは、ORMを使用するときに、より間接的な(そして完全にPythonで型付けされた)方法で宣言されていますが、ORMの設定内にはまだ Table
オブジェクトがあります。
Setting up MetaData with Table objects¶
リレーショナルデータベースで作業する場合、クエリを実行するデータベースの基本的なデータ保持構造は**テーブル**と呼ばれます。SQLAlchemyでは、データベースの「テーブル」は最終的には Table
という同様の名前のPythonオブジェクトで表されます。
SQLAlchemy式言語の使用を開始するには、対象となるすべてのデータベーステーブルを表す Table
オブジェクトを構築する必要があります。 Table
は、 Table
コンストラクタを使用して直接、またはORM Mappedクラス(後で Using ORM Declarative Forms to Define Table Metadata で説明します)を使用して間接的に、プログラムによって構築されます。 reflection と呼ばれる、既存のデータベースからテーブル情報の一部またはすべてをロードするオプションもあります。
どちらのアプローチを使用しても、私たちは常に、 MetaData
オブジェクトとして知られるテーブルを配置するコレクションから始めます。このオブジェクトは本質的に、文字列名にキー設定された一連の Table
オブジェクトを格納するPython辞書を囲む facade です。ORMはこのコレクションをどこで取得するかについていくつかのオプションを提供していますが、私たちには常に、次のように直接作成するオプションがあります:
>>> from sqlalchemy import MetaData
>>> metadata_obj = MetaData()
MetaData
オブジェクトがあれば、いくつかの Table
オブジェクトを宣言することができます。このチュートリアルでは、古典的なSQLAlchemyチュートリアルモデルから始めます。このモデルには、例えばWebサイトのユーザーを格納する user_account
というテーブルと、 user_account
テーブルの行に関連付けられたメールアドレスを格納する address
という関連テーブルがあります。ORM宣言モデルをまったく使用しない場合は、各 Table
オブジェクトを直接構築し、通常はアプリケーションコードでテーブルを参照する方法となる変数にそれぞれを割り当てます。
>>> from sqlalchemy import Table, Column, Integer, String
>>> user_table = Table(
... "user_account",
... metadata_obj,
... Column("id", Integer, primary_key=True),
... Column("name", String(30)),
... Column("fullname", String),
... )
上の例で、データベース内の user_account
テーブルを参照するコードを書きたいときは、Python変数の user_table
を使ってそれを参照します。
Components of Table
¶
Pythonで書かれた Table
構文は、SQL CREATE TABLE文に似ていることがわかります。つまり、テーブル名から始まり、各列をリストします。各列には名前とデータ型があります。上記で使用しているオブジェクトは次のとおりです。
Column
- データベーステーブルの列を表し、それ自身をTable
オブジェクトに代入します。Column
には通常、文字列名と型オブジェクトが含まれます。親のTable
から見たColumn
オブジェクトのコレクションは、通常Table.c
にある連想配列を介してアクセスされます:>>> user_table.c.name Column('name', String(length=30), table=<user_account>) >>> user_table.c.keys() ['id', 'name', 'fullname']
Integer
,:class:_types.String -これらのクラスはSQLデータ型を表し、Column
に渡すことができます。インスタンス化の有無は関係ありません。上記では、”name”列に”30”の長さを与えたいので、String(30)
をインスタンス化します。しかし、”id”と”fullname”にはこれらを指定しなかったので、クラス自体を送ることができます。
See also
MetaData
, Table
および Column
のリファレンスおよびAPIドキュメントは Describing Databases with MetaData にあります。データ型のリファレンスドキュメントは SQL Datatype Objects にあります。
次のセクションでは、特定のデータベース接続に対して DDL を生成する Table
の基本的な関数の1つを説明しますが、まず2番目の Table
を宣言します。
Declaring Simple Constraints¶
例 user_table
の最初の Column
には、 Column.primary_key
パラメータが含まれています。これは、この Column
がこのテーブルの主キーの一部であるべきことを示すための簡単なテクニックです。主キー自体は通常暗黙的に宣言され、 PrimaryKeyConstraint
構文で表されます。これは Table
オブジェクトの Table.primary_key
属性で見ることができます:
>>> user_table.primary_key
PrimaryKeyConstraint(Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False))
最も一般的に明示的に宣言される制約は、データベースの:term:外部キー制約`に対応する :class:`_schema.ForeignKeyConstraint オブジェクトです。互いに関連するテーブルを宣言するとき、SQLAlchemyはこれらの外部キー制約宣言の存在を使用して、それらがCREATE文の中でデータベースに出力されるようにするだけでなく、SQL式の構築を支援します。
対象となるテーブルの1つの列のみを含む ForeignKeyConstraint
は、通常、 ForeignKey
オブジェクトを介して列レベルの省略表記を使用して宣言されます。以下では、 user
テーブルを参照する外部キー制約を持つ2番目のテーブル address
を宣言します。
>>> from sqlalchemy import ForeignKey
>>> address_table = Table(
... "address",
... metadata_obj,
... Column("id", Integer, primary_key=True),
... Column("user_id", ForeignKey("user_account.id"), nullable=False),
... Column("email_address", String, nullable=False),
... )
上の表には3番目の種類の制約もあります。これはSQLでは「NOT NULL」制約で、上では Column.nullable
パラメータを使って示されています。
Tip
ForeignKey
オブジェクトを Column
定義内で使用する場合、その Column
のデータ型を省略することができます。これは、関連する列のデータ型から自動的に推測されます。上の例では、 user_account.id
列の Integer
データ型です。
次のセクションでは、 user
や address
テーブルの完成したDDLを出力して、完成した結果を確認します。
Emitting DDL to the Database¶
データベース内の2つのデータベーステーブルを表すオブジェクト構造を構築しました。ルートの MetaData
オブジェクトから始まり、2つの Table
オブジェクトになり、それぞれが Column
オブジェクトと Constraint
オブジェクトのコレクションを保持します。このオブジェクト構造は、今後CoreとORMの両方で実行するほとんどの操作の中心になります。
この構造体で最初にできる便利なことは、CREATE TABLE文、つまり DDL をSQLiteデータベースに出力して、そこからデータを挿入したり問い合わせたりできるようにすることです。 MetaData
で MetaData.create_all()
メソッドを呼び出し、ターゲットデータベースを参照する Engine
を送信することで、そのために必要なすべてのツールがすでに用意されています。:
>>> metadata_obj.create_all(engine)
BEGIN (implicit)
PRAGMA main.table_...info("user_account")
...
PRAGMA main.table_...info("address")
...
CREATE TABLE user_account (
id INTEGER NOT NULL,
name VARCHAR(30),
fullname VARCHAR,
PRIMARY KEY (id)
)
...
CREATE TABLE address (
id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
email_address VARCHAR NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(user_id) REFERENCES user_account (id)
)
...
COMMIT
前述のDDL作成プロセスには、CREATEを発行する前に各表の存在をテストするSQLite固有のPRAGMA文がいくつか含まれています。トランザクションDDLに対応するために、一連の全ステップもBEGIN/COMMITペアに含まれています。
createプロセスはCREATE文を正しい順序で出力することにも注意します。上記では、FOREIGN KEY制約は存在する user
テーブルに依存しているので、 address
テーブルは2番目に作成されます。より複雑な依存関係のシナリオでは、ALTERを使用して、FOREIGN KEY制約をその後のテーブルに適用することもできます。
MetaData
オブジェクトには MetaData.drop_all()
メソッドもあります。このメソッドは、スキーマ要素を削除するためにCREATEを発行するのと逆の順序でDROP文を発行します。
Using ORM Declarative Forms to Define Table Metadata¶
テーブル・メタデータと、パーシスタンス/オブジェクト・ロード操作で使用されるORMマップ・クラスを一度に宣言できます。
この節では、前の節と同じ :class`_schema.Table` メタデータが宣言テーブルを使って構築されているところを説明します。
ORMを使用する場合、 Table
メタデータを宣言するプロセスは、通常、 mapped クラスを宣言するプロセスと組み合わされます。mappedクラスは、作成したい任意のPythonクラスで、データベーステーブル内の列にリンクされる属性を持ちます。これを実現する方法にはいくつかの種類がありますが、最も一般的なスタイルは declarative として知られており、ユーザ定義クラスと Table
メタデータを一度に宣言できます。
Establishing a Declarative Base¶
ORMを使用する場合、 MetaData
コレクションは存在しますが、それ自体は一般に**DeclarativeBase**と呼ばれるORMのみの構成体に関連付けられています。新しいDeclarativeBaseを取得する最も便利な方法は、SQLAlchemy DeclarativeBase
クラスをサブクラスとする新しいクラスを作成することです:
>>> from sqlalchemy.orm import DeclarativeBase
>>> class Base(DeclarativeBase):
... pass
上記の Base
クラスは、宣言ベースと呼ばれるものです。 Base
のサブクラスである新しいクラスを作成し、適切なクラスレベルのディレクティブと組み合わせると、クラスの作成時にそれぞれが新しい ORMマップクラス として確立され、それぞれが特定の Table
オブジェクトを参照します(ただし、排他的ではありません)。
宣言ベースは、私たちが外部から提供しなかったと仮定して、私たちのために自動的に作成された MetaData
コレクションを参照します。この MetaData
コレクションは、 DeclarativeBase.MetaData
クラスレベル属性を介してアクセスできます。新しいマップされたクラスを作成すると、それぞれがこの MetaData
コレクション内の Table
を参照します:
>>> Base.metadata
MetaData()
宣言ベースは registry
と呼ばれるコレクションも参照します。これはSQLAlchemy ORMの中心的な「マッパー設定」ユニットです。直接アクセスされることはめったにありませんが、このオブジェクトはマッパー設定プロセスの中心となり、ORMにマップされたクラスのセットがこのレジストリを介して相互に調整されます。 MetaData
の場合と同様に、宣言ベースは私たちのために registry
も作成しました(ここでも独自の registry
を渡すオプションがあります)。これは DeclarativeBase.registry
クラス変数を介してアクセスできます:
>>> Base.registry
<sqlalchemy.orm.decl_api.registry object at 0x...>
Declaring Mapped Classes¶
Base
クラスが確立されたので、 user_account
テーブルと address
テーブルのORMマップクラスを新しいクラス User
と Address
で定義できるようになりました。以下では、属性が特定の型としてマップされることを示す特別な型 Mapped
を使用して、 PEP 484 型アノテーションから駆動される、最も現代的な形式の宣言型を説明します:
>>> from typing import List
>>> from typing import Optional
>>> from sqlalchemy.orm import Mapped
>>> from sqlalchemy.orm import mapped_column
>>> from sqlalchemy.orm import relationship
>>> class User(Base):
... __tablename__ = "user_account"
...
... id: Mapped[int] = mapped_column(primary_key=True)
... name: Mapped[str] = mapped_column(String(30))
... fullname: Mapped[Optional[str]]
...
... addresses: Mapped[List["Address"]] = relationship(back_populates="user")
...
... def __repr__(self) -> str:
... return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"
>>> class Address(Base):
... __tablename__ = "address"
...
... id: Mapped[int] = mapped_column(primary_key=True)
... email_address: Mapped[str]
... user_id = mapped_column(ForeignKey("user_account.id"))
...
... user: Mapped[User] = relationship(back_populates="addresses")
...
... def __repr__(self) -> str:
... return f"Address(id={self.id!r}, email_address={self.email_address!r})"
上記の2つのクラス、 User
and Address
は、 ORM Mapped Classes と呼ばれるようになり、後で説明するORMパーシステンスとクエリ操作で使用できるようになりました。これらのクラスの詳細は次のとおりです。
各クラスは、宣言的なマッピングプロセスの一部として生成された
Table
オブジェクトを参照します。このオブジェクトは、DeclarativeBase.__tablename__
属性に文字列を割り当てることによって名前が付けられます。クラスが作成されると、この生成されたTable
はDeclarativeBase.__table__
属性から利用できます。
前述したように、この形式は Declarative Table Configuration として知られています。いくつかの代替宣言スタイルの1つは、代わりに
Table
オブジェクトを直接構築し、それをDeclarativeBase.__table__
に直接 代入 します。このスタイルは Declarative with Imperative Table として知られています。
Table
内の列を示すには、Mapped
型に基づいた型注釈と組み合わせて、mapped_column()
構文を使用します。このオブジェクトはColumn
オブジェクトを生成し、Table
の構築に適用されます。
単純なデータ型を持ち、他のオプションを持たない列に対しては、
Mapped
型アノテーションのみを指定することができます。これには、Integer
やString
を意味するint
やstr
のような単純なPython型を使用します。宣言型マッピングプロセス内でのPython型の解釈方法のカスタマイズは非常に自由です。背景については Using Annotated Declarative Table (Type Annotated Forms for mapped_column()) と Customizing the Type Map を参照してください。
列は、
Optional[<typ>]
型アノテーション(またはそれと同等のもの、<typ> | None
またはUnion[<typ>, None]
)の有無に基づいて、 “nullable” or “not null” として宣言できます。mapped_column.nullable
パラメータも明示的に使用できます(アノテーションのオプション性と一致する必要はありません)。
明示的な型注釈の使用は**完全にオプション**です。注釈なしで
mapped_column()
を使用することもできます。この形式を使用する場合は、Integer
やString
のようなより明示的な型オブジェクトを使用し、各mapped_column()
構造内で必要に応じてnullable=False
を使用します。
2つの追加属性、
User.addresses
やAddress.user
は、relationship()
と呼ばれる別の種類の属性を定義します。これは、図に示すように同様の注釈認識設定スタイルを特徴としています。relationship()
構文については、 Working with ORM Related Objects で詳しく説明されています。
独自のメソッドを宣言しない場合、クラスには自動的に
__init__()
メソッドが与えられます。このメソッドのデフォルトの形式では、すべての属性名をオプションのキーワード引数として受け入れます。>>> sandy = User(name="sandy", fullname="Sandy Cheeks")
位置引数とデフォルトのキーワード値を持つ引数を提供する完全な機能を備えた
__init__()
メソッドを自動的に生成するために、 Declarative Dataclass Mapping で導入されたデータクラス機能を使用することができます。もちろん、明示的な__init__()
メソッドを使用することも常にオプションです。
読みやすい文字列出力を得るために、
__repr__()
メソッドが追加されています。これらのメソッドがここにある必要はありません。__init__()
の場合と同様に、 dataclasses 機能を使って、自動的に__repr__()
メソッドを生成することができます。
See also
ORM Mapping Styles - 様々なORM設定スタイルの完全な背景です。
Declarative Mapping - 宣言型クラスマッピングの概要
Declarative Table with mapped_column() - mapped_column()
と Mapped
を使用して、Declarativeを使用するときにマップされる Table
内の列を定義する方法の詳細。
Emitting DDL to the database from an ORM mapping¶
ORMにマップされたクラスは MetaData
コレクションに含まれる Table
オブジェクトを参照するので、DeclarativeBaseを指定してDDLを出力するには、以前に Emitting DDL to the Database で説明したのと同じプロセスを使用します。この例では、SQLiteデータベース内にすでに user
テーブルと address
テーブルが生成されています。まだ生成されていなければ、 DeclarativeBase.MetaData
属性からコレクションにアクセスし、以前と同様に MetaData.create_all()
を使用することで、ORM DeclarativeBaseクラスに関連付けられた MetaData
を自由に使用できます。この場合、PRAGMAステートメントが実行されますが、新しいテーブルはすでに存在しているため生成されません。
>>> Base.metadata.create_all(engine)
BEGIN (implicit)
PRAGMA main.table_...info("user_account")
...
PRAGMA main.table_...info("address")
...
COMMIT
Table Reflection¶
テーブルメタデータの操作に関するセクションを締めくくるために、セクションの冒頭で述べた別の操作、 テーブルリフレクション について説明します。テーブルリフレクションとは、データベースの現在の状態を読み取ることによって、 Table
および関連するオブジェクトを生成するプロセスを指します。これまでのセクションでは、Pythonで Table
オブジェクトを宣言してきましたが、データベースにDDLを出力してそのようなスキーマを生成するオプションがあります。リフレクションプロセスは、既存のデータベースから開始して、そのデータベース内のスキーマを表すPython内のデータ構造を生成するという2つのステップを逆に実行します。
リフレクションの例として、このドキュメントの前のセクションで手動で作成した some_table
オブジェクトを表す新しい Table
オブジェクトを作成します。これを実行する方法にもいくつかの種類がありますが、最も基本的なのは、テーブルの名前とそれが属する MetaData
コレクションを指定して Table
オブジェクトを作成し、個々の Column
オブジェクトと Constraint
オブジェクトを示すのではなく、 Table.autoload_with
パラメータを使用してターゲットの Engine
に渡すことです。:
>>> some_table = Table("some_table", metadata_obj, autoload_with=engine)
BEGIN (implicit)
PRAGMA main.table_...info("some_table")
[raw sql] ()
SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE name = ? AND type in ('table', 'view')
[raw sql] ('some_table',)
PRAGMA main.foreign_key_list("some_table")
...
PRAGMA main.index_list("some_table")
...
ROLLBACK
プロセスの最後に、テーブル内に存在する Column
オブジェクトについての情報が some_table
オブジェクトに格納されます。このオブジェクトは、明示的に宣言した Table
とまったく同じ方法で使用できます:
>>> some_table
Table('some_table', MetaData(),
Column('x', INTEGER(), table=<some_table>),
Column('y', INTEGER(), table=<some_table>),
schema=None)
See also
テーブルとスキーマのリフレクションの詳細については Reflecting Database Objects を参照してください。
ORM関連のテーブルリフレクションについては、 Mapping Declaratively with Reflected Tables 節に利用可能なオプションの概要が含まれています。
Next Steps¶
これで、2つのテーブルが存在するSQLiteデータベースと、 Connection
および/またはORM Session
を介してこれらのテーブルと対話するために使用できるCoreおよびORMテーブル指向の構成体が準備できました。次のセクションでは、これらの構造を使用してデータを作成、操作、および選択する方法について説明します。