SQLAlchemy と Oracle Database 23c の無料統合ツアー

SQLAlchemy と Oracle Database 23c の無料統合ツアー

1. SQLAlchemy とは何ですか?

SQLAlchemy は、アプリケーション開発者に SQL のフルパワーと柔軟性を提供する Python SQL ツールキットおよびオブジェクト リレーショナル マッパーです。

これは、効率的で高パフォーマンスのデータベース アクセス向けに設計され、シンプルな Python ドメイン言語に適合した、よく知られたエンタープライズ レベルの永続パターンのセットを提供します。

公式サイトアドレス: https: //www.sqlalchemy.org/

SQLAlchemy SQL ツールキットとオブジェクト リレーショナル マッパーは、データベースと Python を操作するための包括的なツール セットです。個別に使用することも、組み合わせて使用​​することもできる、いくつかの異なる機能領域があります。その主なコンポーネントを次の図に示します。コンポーネントの依存関係はレイヤーに編成されています。

ここに画像の説明を挿入します

上記の SQLAlchemy の 2 つの最も重要なフロントエンド部分は、オブジェクト リレーショナル マッパー (ORM) とコアです。

Core には、SQLAlchemy の幅広い SQL およびデータベースの統合および記述サービスが含まれており、その中で最も有名なものは SQL 式言語です。

SQL 式言語自体は ORM パッケージから独立したツールキットであり、構成可能なオブジェクトで表される SQL 式を構築するためのシステムを提供し、特定のトランザクションの範囲内でターゲット データベースに対して「実行」して結果を返すことができます。セットです。挿入、更新、および削除 (つまり DML) は、これらのステートメントを表す SQL 式オブジェクトと、各ステートメントで使用されるパラメーターを表すディクショナリを渡すことによって実装されます。

ORM はコア上に構築され、データベース スキーマにマップされたドメイン オブジェクト モデルを使用する方法を提供します。ORM を使用する場合、SQL ステートメントは Core を使用する場合とほぼ同じ方法で構築されますが、DML タスク (ここでは、データベース内のビジネス オブジェクトの永続化) は、作業単位と呼ばれるパターンを使用して自動化されます。 UPDATE および DELETE は、可変オブジェクトの状態変更を構築し、それらのオブジェクトに対して呼び出します。SELECT ステートメントは、ORM 固有の自動化およびオブジェクト中心のクエリ機能によって強化されています。

Core および SQL Expression Language はデータベースのスキーマ中心のビューと不変性指向のプログラミング パラダイムを提供しますが、ORM はこの基盤に基づいてデータベースのドメイン中心のビューを構築し、そのプログラミング パラダイムはより明示的にオブジェクト指向であり、可変性に依存します。 。リレーショナル データベース自体は変更可能なサービスであるため、違いは、Core/SQL Expression 言語がコマンド指向であるのに対し、ORM は状態指向であるという点です。

参考ドキュメント: https://docs.sqlalchemy.org/en/20/intro.html

2. Oracle Database 23c Freeとは何ですか?

Oracle Database 23c Free は、リレーショナル データベース サービス Oracle Database 23c の開発者バージョンです (2023 年 9 月の時点では正式にはリリースされていません)。このコンテナ イメージには、1 つの PDB を持つマルチテナント構成のデフォルト データベースが含まれています。

3.Oracle Database 23cを無料で実行する

SQLAlchemy のバックエンド データベースとして使用するには、まず Oracle Database 23c Free を起動する必要があります。

マウントパスを作成しますので、それぞれの環境に合わせて変更してください。

sudo mkdir -p /u01/data/oracledb && sudo chown oracle:oracle /u01/data/oracledb && sudo chmod 777 /u01/data/oracledb

Oracle Database Free Release 23cを実行中、

docker run --name oracledb23c --restart=always \
-p 1521:1521 \
-e ORACLE_PWD=<your database passwords> \
-v /u01/data/oracledb:/opt/oracle/oradata \
-d container-registry.oracle.com/database/free:latest

注意: 入力するパスワードは8文字以上で、少なくとも1つの大文字、1つの小文字、および1つの数字[0~9]を含むことをお薦めします。SYS、SYSTEM、および PDBADMIN アカウントは同じパスワードを使用します。

インストールログを確認して、

docker logs oracledb23c -f

--- output
(略)
Disconnected from Oracle Database 23c Free, Release 23.0.0.0.0 - Developer-Release
Version 23.2.0.0.0
The Oracle base remains unchanged with value /opt/oracle
The Oracle base remains unchanged with value /opt/oracle
#########################
DATABASE IS READY TO USE!
#########################
The following output is now a tail of the alert.log:

XDB initialized.
ALTER PLUGGABLE DATABASE FREEPDB1 SAVE STATE
Completed: ALTER PLUGGABLE DATABASE FREEPDB1 SAVE STATE
2023-04-08T01:36:03.608797+00:00
ALTER SYSTEM SET control_files='/opt/oracle/oradata/FREE/control01.ctl' SCOPE=SPFILE;
2023-04-08T01:36:03.625859+00:00
ALTER SYSTEM SET local_listener='' SCOPE=BOTH;
ALTER PLUGGABLE DATABASE FREEPDB1 SAVE STATE
Completed: ALTER PLUGGABLE DATABASE FREEPDB1 SAVE STATE
---

コンテナ内から接続し、

docker exec -it oracledb23c sqlplus / as sysdba

ここに画像の説明を挿入します

docker exec -it oracledb23c sqlplus sys/<your_password>@FREE as sysdba

ここに画像の説明を挿入します

docker exec -it oracledb23c sqlplus system/<your_password>@FREE

ここに画像の説明を挿入します

docker exec -it oracledb23c sqlplus pdbadmin/<your_password>@FREEPDB1

ここに画像の説明を挿入します

コンテナの外から接続し、

# To connect to the database at the CDB$ROOT level as sysdba:
  $ sqlplus sys/<your password>@//localhost:<port mapped to 1521>/FREE as sysdba
# To connect as non sysdba at the CDB$ROOT level:
  $ sqlplus system/<your password>@//localhost:<port mapped to 1521>/FREE
# To connect to the default Pluggable Database (PDB) within the FREE Database:
  $ sqlplus pdbadmin/<your password>@//localhost:<port mapped to 1521>/FREEPDB1

以降の使用を容易にするために、sys または system ユーザーを使用して FREEPDB1 に接続し、pdbadmin ユーザーに dba 権限を付与します。

grant dba to pdbadmin;

4. SQLAlchemy 統合チュートリアルを学ぶ

SQLAlchemy の新規ユーザーは、SQLAlchemy 統合チュートリアルから始める必要があります。このチュートリアルでは、ORM を使用する場合、または単に Core を使用する場合に Alchemist が知っておく必要があるすべての内容が説明されています。

4-1. 依存ライブラリのインストール

まず、pip を介して SQLAlchemy ライブラリをインストールします。

pip install SQLAlchemy

python-oracledb ドライバを使用すると、Python 3 アプリケーションが Oracle Database の python-oracledb ライブラリを通じて Oracle Database に接続できるようになります。

pip install oracledb

参考ドキュメント: https://oracle.github.io/python-oracledb/

https://python-oracledb.readthedocs.io/en/latest/user_guide/installation.html

4-2. 接続の確立 - エンジン

データベースに接続するすべての SQLAlchemy アプリケーションは、エンジンを使用する必要があります。この短いセクションには誰もが楽しめる内容があります。

SQLAlchemy アプリケーションの始まりは、Engine という名前のオブジェクトです。このオブジェクトは、特定のデータベース接続の中心的なソースとして機能し、これらのデータベース接続用のファクトリと、接続プールと呼ばれる保持スペースを提供します。通常、エンジンは特定のデータベース サーバーに対して 1 回だけ作成されるグローバル オブジェクトであり、データベース ホストまたはバックエンドへの接続方法を記述する URL 文字列で構成されます。

このチュートリアルでは、Oracle Database 23c データベースを使用します。エンジンは create_engine() 関数を使用して作成されます。

from sqlalchemy import create_engine
engine = create_engine("oracle+oracledb://pdbadmin:<YOUR_PASSWORD>@<YOUR_IP>:1521?service_name=FREEPDB1", echo=True)

接続文字列 URL 形式の説明:

oracle+oracledb://user:pass@hostname:port[/dbname][?service_name=<service>[&key=value&key=value...]]

create_engine の主な引数は、エンジンに 3 つの重要な事実を示す文字列 URL です。

  • どのような種類のデータベースと通信しているのでしょうか? これは上記のオラクル部分であり、SQLAlchemy の方言と呼ばれるオブジェクトにリンクされています。
  • どのような DBAPI を使用しますか? Python DBAPI は、特定のデータベースと対話するために SQLAlchemy によって使用されるサードパーティ ドライバーです。この例では、oracledb という名前を使用します。
  • データベースを見つけるにはどうすればよいでしょうか? ?service_name=FREEPDB1この例では、URL にFREEPDB1 PDB ライブラリを使用することを指定するフレーズが含まれています。

遅延接続:
create_engine() が最初に戻ったとき、エンジンは実際にはデータベースへの接続を試行していません。これは、データベース上でタスクを実行するよう初めて要求されたときにのみ発生します。これは、遅延初期化と呼ばれるソフトウェア設計パターンです。

4-3. トランザクションとDBAPIの利用

Engine オブジェクトを配置したら、Engine とその主要なインタラクション エンドポイントである Connection と Result を詳しく見てみましょう。これらのオブジェクトに対して、Session と呼ばれる追加の ORM ファサードも導入します。

ORM を使用する場合、エンジンはセッションと呼ばれる別のオブジェクトによって管理されます。最新の SQLAlchemy のセッションは、トランザクションと SQL 実行モードに重点を置いています。これは、以下で説明する接続と基本的に同じです。そのため、このセクションはコア中心ですが、ここにあるすべての概念は本質的に ORM の使用にも関連しており、すべての ORM の学習や、使用。Connection で使用される実行モードは、このセクションの最後で Session の実行モードと比較されます。

SQLAlchemy 式言語 (SQLAlchemy の主な機能) については説明していないため、このパッケージでは text() コンストラクトと呼ばれる単純なコンストラクトを使用します。これにより、SQL ステートメントをテキスト SQL として作成できます。ほとんどのタスクでは、SQLAlchemy を日常的に使用するテキスト SQL は原則ではなく例外ですが、常に完全に利用可能ですのでご安心ください。

4-3-1. 接続の取得

ユーザー側の観点から見ると、Engine オブジェクトの唯一の目的は、Connection と呼ばれるデータベース接続ユニットを提供することです。Core を直接操作する場合、データベースとのすべてのやり取りは Connection オブジェクトによって行われます。Connection はデータベースに対するオープン リソースを表すため、このオブジェクトの使用を常に特定のコンテキストに制限したいと考えています。これを行うための最良の方法は、with ステートメントとも呼ばれる Python コンテキスト マネージャー フォームを使用することです。以下では、テキスト SQL ステートメントを使用して「Hello World」を説明します。テキスト SQL は、text() と呼ばれる構造を使用して発行されます。これについては、後で詳しく説明します。

まずはエンジンを作り、

from sqlalchemy import create_engine, text

# 创建引擎
engine = create_engine("oracle+oracledb://pdbadmin:<YOUR_PASSWORD>@<YOUR_IP>:1521?service_name=FREEPDB1", echo=True)

サンプルコード、

with engine.connect() as conn:
    result = conn.execute(text("select 'hello world'"))
    print(result.all())

入力結果は以下の通りです。

2023-09-16 19:51:00,475 INFO sqlalchemy.engine.Engine select sys_context( 'userenv', 'current_schema' ) from dual
2023-09-16 19:51:00,476 INFO sqlalchemy.engine.Engine [raw sql] {}
2023-09-16 19:51:00,481 INFO sqlalchemy.engine.Engine SELECT value FROM v$parameter WHERE name = 'compatible'
2023-09-16 19:51:00,481 INFO sqlalchemy.engine.Engine [raw sql] {}
2023-09-16 19:51:00,490 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 19:51:00,490 INFO sqlalchemy.engine.Engine select 'hello world'
2023-09-16 19:51:00,491 INFO sqlalchemy.engine.Engine [generated in 0.00083s] {}
[('hello world',)]
2023-09-16 19:51:00,491 INFO sqlalchemy.engine.Engine ROLLBACK

上記の例では、コンテキスト マネージャーはデータベース接続を提供し、トランザクション内の操作も構築します。Python DBAPI のデフォルトの動作では、トランザクションは常に進行中です。接続スコープが解放されると、ROLLBACK が発行されてトランザクションが終了します。トランザクションは自動的にはコミットされません。データをコミットしたい場合は、次のセクションで説明するように、通常は Connection.commit() を呼び出す必要があります。

「オートコミット」モードは特別な状況で使用できます。これについては、「DBAPI 自動コミットを含むトランザクション分離レベルの設定」セクションで説明します。

SELECT の結果は、Result と呼ばれるオブジェクトにも返されます。これについては後で説明しますが、現時点では、このオブジェクトが「connect」ブロック内で使用され、「connect」ブロックの範囲外にないことを確認することが最善であることを付け加えておきます。私たちの接続外部転送。

4-3-2. 変更を送信する

DBAPI 接続は自動コミットされないことを学びました。データを送信したい場合はどうすればよいですか? 上記の例を変更して、テーブルを作成し、データを挿入してから、Connection オブジェクトを取得するブロック内で呼び出される Connection.commit() メソッドを使用してトランザクションをコミットします。

with engine.connect() as conn:
    conn.execute(text("CREATE TABLE some_table (x int, y int)"))
    conn.execute(
        text("INSERT INTO some_table (x, y) VALUES (:x, :y)"),
        [{"x": 1, "y": 1}, {"x": 2, "y": 4}],
    )
    conn.commit()

出力結果は以下のようになります。

2023-09-16 19:53:42,935 INFO sqlalchemy.engine.Engine select sys_context( 'userenv', 'current_schema' ) from dual
2023-09-16 19:53:42,936 INFO sqlalchemy.engine.Engine [raw sql] {}
2023-09-16 19:53:42,942 INFO sqlalchemy.engine.Engine SELECT value FROM v$parameter WHERE name = 'compatible'
2023-09-16 19:53:42,942 INFO sqlalchemy.engine.Engine [raw sql] {}
2023-09-16 19:53:42,950 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 19:53:42,951 INFO sqlalchemy.engine.Engine CREATE TABLE some_table (x int, y int)
2023-09-16 19:53:42,951 INFO sqlalchemy.engine.Engine [generated in 0.00095s] {}
2023-09-16 19:53:42,959 INFO sqlalchemy.engine.Engine INSERT INTO some_table (x, y) VALUES (:x, :y)
2023-09-16 19:53:42,959 INFO sqlalchemy.engine.Engine [generated in 0.00040s] [{'x': 1, 'y': 1}, {'x': 2, 'y': 4}]
2023-09-16 19:53:43,119 INFO sqlalchemy.engine.Engine COMMIT

上記では、「CREATE TABLE」ステートメント [1] とパラメーター化された「INSERT」ステートメントの 2 つの通常のトランザクション SQL ステートメントを発行しました (上記のパラメーター化された構文については、以下の「複数のパラメーターの送信」で説明します)。ブロック内の完了した作業をコミットしたいので、コミットされたトランザクションの Connection.commit() メソッドを呼び出します。ブロック内でこのメソッドを呼び出した後、さらに SQL ステートメントの実行を続けることができ、必要に応じて、後続のステートメントに対して Connection.commit() を再度呼び出すことができます。SQLAlchemy では、このスタイルを「いつでもコミット」と呼んでいます。

データをコミットする別の方法があります。つまり、「接続」ブロックをトランザクション ブロックとして事前に宣言することができます。この操作モードでは、Engine.connect() メソッドの代わりに Engine.begin() メソッドを使用して接続を取得します。このメソッドは接続のスコープを管理し、トランザクション内のすべてを含めます。ブロックが成功した場合には最後に COMMIT を使用し、例外がスローされた場合には ROLLBACK を使用します。このスタイルは 1 回だけ呼び出して呼び出すことができます。

with engine.begin() as conn:
    conn.execute(
        text("INSERT INTO some_table (x, y) VALUES (:x, :y)"),
        [{"x": 6, "y": 8}, {"x": 9, "y": 10}],
    )

出力結果は以下のようになります。

2023-09-16 19:57:10,476 INFO sqlalchemy.engine.Engine select sys_context( 'userenv', 'current_schema' ) from dual
2023-09-16 19:57:10,476 INFO sqlalchemy.engine.Engine [raw sql] {}
2023-09-16 19:57:10,483 INFO sqlalchemy.engine.Engine SELECT value FROM v$parameter WHERE name = 'compatible'
2023-09-16 19:57:10,483 INFO sqlalchemy.engine.Engine [raw sql] {}
2023-09-16 19:57:10,491 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 19:57:10,492 INFO sqlalchemy.engine.Engine INSERT INTO some_table (x, y) VALUES (:x, :y)
2023-09-16 19:57:10,492 INFO sqlalchemy.engine.Engine [generated in 0.00053s] [{'x': 6, 'y': 8}, {'x': 9, 'y': 10}]
2023-09-16 19:57:10,494 INFO sqlalchemy.engine.Engine COMMIT

「一度開始」スタイルは、より簡潔であり、ブロック全体の意図を前もって示すため、多くの場合好まれます。ただし、このチュートリアルでは、デモンストレーション目的ではより柔軟なため、通常は「好きなように送信」スタイルを使用します。

「BEGIN(暗黙的)」とは何ですか?
トランザクション ブロックの先頭にあるログ行「BEGIN (暗黙的)」に気づいたかもしれません。ここでの「暗黙的」とは、SQLAlchemy が実際にはデータベースにコマンドを送信せず、これが DBAPI の暗黙的なトランザクションの開始であると考えるだけであることを意味します。たとえば、このイベントをインターセプトするイベント フックを登録できます。

DDL は、スキーマ レベルの構造 (テーブルなど) を作成、変更、または削除するようにデータベースに指示する SQL のサブセットを指します。多くのデータベースはトランザクション DDL を使用し、トランザクションがコミットされるまでスキーマの変更が発生しないようにするため、COMMIT で終わるトランザクション ブロック内に「CREATE TABLE」などの DDL を配置することをお勧めします。ただし、後で説明するように、通常は SQLAlchemy に高レベルの操作の一部として DDL シーケンスを実行させるため、通常は COMMIT について心配する必要はありません。

4-3-3. ステートメント実行の基礎知識

Connection.execute() というメソッドを text() というオブジェクトと組み合わせて使用​​し、データベースに対して SQL ステートメントを実行し、Result を返す例をいくつか見てきました。このセクションでは、これらのコンポーネントの仕組みと相互作用について詳しく説明します。

このセクションの多くは、Core と同じ Result インターフェイスを使用するなど、Connection.execute() と非常によく似た動作をする Session.execute() メソッドを使用する場合の最新の ORM の使用法に等しく当てはまります。

4-3-3-1. 行の取得

まず、以前に挿入した行を使用して作成したテーブルに対してテキスト SELECT ステートメントを実行して、Result オブジェクトを詳しく説明します。

with engine.connect() as conn:
    result = conn.execute(text("SELECT x, y FROM some_table"))
    for row in result:
        print(f"x: {row.x}  y: {row.y}")

出力結果は以下のようになります。

2023-09-16 20:02:31,277 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 20:02:31,278 INFO sqlalchemy.engine.Engine SELECT x, y FROM some_table
2023-09-16 20:02:31,278 INFO sqlalchemy.engine.Engine [generated in 0.00102s] {}
x: 1  y: 1
x: 2  y: 4
x: 6  y: 8
x: 9  y: 10
2023-09-16 20:02:31,283 INFO sqlalchemy.engine.Engine ROLLBACK

上では、「SELECT」文字列を実行してテーブル内のすべての行を選択しました。返されるオブジェクトは Result と呼ばれ、結果行の反復可能オブジェクトを表します。

Result には、すべての Row オブジェクトのリストを返す、前に紹介した Result.all() メソッドなど、行を取得および変換するためのメソッドが多数あります。また、Python イテレータ インターフェイスも実装されているため、Row オブジェクトのコレクションを直接反復処理できます。

Row オブジェクト自体は、Python の名前付きタプルのように動作します。以下に、行にアクセスするさまざまな方法を示します。

  • タプル割り当て - これは最も慣用的な Python スタイルで、変数を受け取ったときに各行に位置的に割り当てます。

    result = conn.execute(text("select x, y from some_table"))
    
    for x, y in result:
        ...
    
  • 整数インデックス - タプルは Python シーケンスであるため、通常の整数を使用してアクセスすることもできます。

    result = conn.execute(text("select x, y from some_table"))
    
    for row in result:
        x = row[0]
    
  • プロパティ名-これらはPythonの名前付きタプルであるため、タプルには各列名に一致する動的なプロパティ名があります。これらの名前は通常、SQL ステートメントが各行の列に割り当てる名前です。これらは一般にかなり予測可能であり、タグを介して制御することもできますが、あまり定義されていない場合には、データベース固有の動作の影響を受ける可能性があります。

    result = conn.execute(text("select x, y from some_table"))
    
    for row in result:
        y = row.y
    
        # illustrate use with Python f-strings
        print(f"Row: {row.x} {y}")
    
  • マッピング アクセス - 行を Python マッピング オブジェクト (基本的に Python の汎用 dict オブジェクト インターフェイスの読み取り専用バージョン) として受け取るには、Result を MappingResult 修飾子を使用してオブジェクトに変換できます。これは、辞書のような RowMapping を生成する結果オブジェクトです。代わりにオブジェクトを行オブジェクト:

    result = conn.execute(text("select x, y from some_table"))
    
    for dict_row in result.mappings():
        x = dict_row["x"]
        y = dict_row["y"]
    
4-3-3-2: パラメータの送信

前の INSERT の例で見たように、SQL ステートメントには、ステートメント自体と一緒に渡されるデータが伴うことがよくあります。したがって、Connection.execute() メソッドは、バインドされたパラメーターと呼ばれるパラメーターも受け入れます。基本的な例としては、関数に渡される特定の値より大きい "y" 値を持つ行な​​ど、特定の条件を満たす行のみに SELECT ステートメントを制限したい場合などが考えられます。

これを達成して、SQL ステートメントを固定したままにし、ドライバーが値を正しくクリーンできるようにするには、ステートメントに WHERE 条件を追加し、「y」という名前の新しいパラメーターを指定します。text() コンストラクトはコロン形式「:y」を使用します。 「これを受け入れてください。次に、「 :y 」の実際の値が、2 番目の引数として辞書の形式で Connection.execute() に渡されます。

with engine.connect() as conn:
    result = conn.execute(text("SELECT x, y FROM some_table WHERE y > :y"), {"y": 2})
    for row in result:
        print(f"x: {row.x}  y: {row.y}")

出力結果は以下のようになります。

2023-09-16 20:07:23,153 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 20:07:23,153 INFO sqlalchemy.engine.Engine SELECT x, y FROM some_table WHERE y > :y
2023-09-16 20:07:23,154 INFO sqlalchemy.engine.Engine [generated in 0.00075s] {'y': 2}
x: 2  y: 4
x: 6  y: 8
x: 9  y: 10
2023-09-16 20:07:23,165 INFO sqlalchemy.engine.Engine ROLLBACK

ログに記録された SQL 出力では、バインド パラメーター :y が SQLite データベースに送信されるときに疑問符に変換されることがわかります。これは、SQLite データベース ドライバーが、DBAPI 仕様で許可されている 6 つの異なる形式のうちの 1 つである「qmark パラメータ スタイル」と呼ばれる形式を使用しているためです。SQLAlchemy は、コロンを使用してこれらの形式を単一の「名前付き」形式に抽象化します。

常にバインドされたパラメーターを使用する:
このセクションの冒頭で述べたように、テキスト SQL は SQLAlchemy の通常の使用方法ではありません。ただし、テキスト SQL を使用する場合、整数や日付などの非文字列であっても、Python テキスト値を SQL 文字列に直接文字列化せず、常にパラメーターを使用する必要があります。これは、データが信頼できない場合に SQL インジェクション攻撃を回避する方法として最もよく知られています。ただし、SQLAlchemy ダイアレクトや DBAPI がバックエンドへの入力を適切に処理できるようになります。プレーン テキスト SQL の使用例に加えて、SQLAlchemy のコア式 API により、Python リテラル値が必要に応じてバインド パラメーターとして渡されることが保証されます。

4-3-3-3. 複数のパラメータを送信する

変更をコミットする例では、INSERT ステートメントを実行しました。複数の行を一度にデータベースに挿入できたように見えます。「INSERT」、「UPDATE」、「DELETE」などの DML ステートメントの場合、単一のディクショナリの代わりにディクショナリのリストを渡すことで、複数のパラメータ セットを Connection.execute() メソッドに送信できます。これは、単一のディクショナリ SQL ステートメントがパラメータセットごとに 1 回ずつ、複数回呼び出す必要があります。この実行メソッドは、executemany と呼ばれます。

with engine.connect() as conn:
    conn.execute(
        text("INSERT INTO some_table (x, y) VALUES (:x, :y)"),
        [{"x": 11, "y": 12}, {"x": 13, "y": 14}],
    )
    conn.commit()

出力結果は以下のようになります。

2023-09-16 20:09:13,761 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 20:09:13,761 INFO sqlalchemy.engine.Engine INSERT INTO some_table (x, y) VALUES (:x, :y)
2023-09-16 20:09:13,762 INFO sqlalchemy.engine.Engine [cached since 723.3s ago] [{'x': 11, 'y': 12}, {'x': 13, 'y': 14}]
2023-09-16 20:09:13,763 INFO sqlalchemy.engine.Engine COMMIT

上記の操作は、複数の行にわたるパフォーマンスを向上させるために操作が最適化されていることを除き、パラメーター セットごとに特定の INSERT ステートメントを 1 回実行するのと同じです。

"execute" と "executemany" の主な動作の違いは、後者では、ステートメントに RETURNING 句が含まれている場合でも、結果行を返すことがサポートされていないことです。唯一の例外は、コアの insert() コンストラクトの使用です (このチュートリアルの後半で「INSERT ステートメントの使用」で説明します)。これは、Insert.returning() メソッドを使用して戻ることも示します。この場合、SQLAlchemy は特別なロジックを使用して INSERT ステートメントを再編成し、RETURNING をサポートしながら多くの行に対して呼び出しできるようにします。

関連項目:
executemany - 用語集では、ほとんどの「executemany」実行に使用される DBAPI レベルのcursor.executemany() メソッドについて説明します。
INSERT ステートメントの「複数の値の挿入」動作 - 「エンジンと接続の使用」では、Insert.returning() が「executemany」実行を通じて結果セットを渡すために使用する特殊なロジックについて説明します。

4-3-4. ORMセッションを利用して実行する

前述したように、上記のパターンと例のほとんどは ORM にも適用されるため、ここではこの使用法について説明します。これにより、チュートリアルが進むにつれて、コアと ORM が一緒に使用されるという観点から各パターンを説明できるようになります。

ORM を使用する場合の基本的なトランザクション/データベース対話オブジェクトはセッションと呼ばれます。最新の SQLAlchemy では、このオブジェクトは Connection と非常によく似た方法で使用されます。実際、Session を使用する場合、SQL を発行するために内部で使用される Connection を指します。

セッションが非 ORM 構造で使用される場合、セッションはそれに渡す SQL ステートメントを渡します。これは通常、接続が直接行うこととあまり変わらないため、ここで既に学習した単純なテキスト SQL 操作を使用できます。 . 説明してください。

セッションにはいくつかの異なる作成モードがありますが、ここでは最も基本的なモードを説明します。これは、接続がどのように使用されるかを正確に追跡する、つまりコンテキスト マネージャーで接続を構築します。

from sqlalchemy.orm import Session

stmt = text("SELECT x, y FROM some_table WHERE y > :y ORDER BY x, y")
with Session(engine) as session:
    result = session.execute(stmt, {"y": 6})
    for row in result:
        print(f"x: {row.x}  y: {row.y}")

出力結果は以下のようになります。

2023-09-16 20:12:26,727 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 20:12:26,727 INFO sqlalchemy.engine.Engine SELECT x, y FROM some_table WHERE y > :y ORDER BY x, y
2023-09-16 20:12:26,729 INFO sqlalchemy.engine.Engine [generated in 0.00047s] {'y': 6}
x: 6  y: 8
x: 9  y: 10
x: 11  y: 12
x: 13  y: 14
2023-09-16 20:12:26,732 INFO sqlalchemy.engine.Engine ROLLBACK

上記の例は、パラメーターの送信に関する前のセクションの例と比較できます。 conn としての Engine.connect() の呼び出しを、セッションとしての Session(engine) で直接置き換えてから、Session.execute() メソッドを使用します。そうします。

さらに、Connection と同様に、Session には、次の図に示すように、Session.commit() メソッドを使用した「従量課金制」の動作があり、テキストの UPDATE ステートメントを使用してデータ内の何かを変更します。

with Session(engine) as session:
    result = session.execute(
        text("UPDATE some_table SET y=:y WHERE x=:x"),
        [{"x": 9, "y": 11}, {"x": 13, "y": 15}],
    )
    session.commit()

出力結果は以下のようになります。

2023-09-16 20:14:15,791 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 20:14:15,792 INFO sqlalchemy.engine.Engine UPDATE some_table SET y=:y WHERE x=:x
2023-09-16 20:14:15,792 INFO sqlalchemy.engine.Engine [generated in 0.00035s] [{'y': 11, 'x': 9}, {'y': 15, 'x': 13}]
2023-09-16 20:14:15,804 INFO sqlalchemy.engine.Engine COMMIT

上記では、「複数パラメータの送信」で導入されたバインド パラメータ、「executemany」実行スタイルを使用して UPDATE ステートメントを呼び出し、「いつでも送信」コミットでブロックを終了しました。

ヒント:
セッションは、トランザクション終了後、実際には Connection オブジェクトを保持しません。次回データベースに対して SQL を実行する必要があるとき、エンジンから新しい接続を取得します。

参照:
セッションの使用の基本- Session オブジェクトの基本的な作成と使用パターンの紹介。

4-3-5. データベースメタデータの利用

エンジンと SQL 実行が停止したら、Alchemy を開始する準備が整いました。SQLAlchemy Core と ORM の中核要素は SQL 式言語であり、これにより SQL クエリの滑らかで構成可能な構築が可能になります。これらのクエリの基礎は、テーブルや列などのデータベースの概念を表す Python オブジェクトです。これらのオブジェクトは総称してデータベース メタデータと呼ばれます。

SQLAlchemy のデータベース メタデータの最も一般的な基本オブジェクトは、MetaData、Table、Column と呼ばれます。次のセクションでは、これらのオブジェクトをコア指向スタイルと ORM 指向スタイルの両方で使用する方法について説明します。

ORM読者の皆様、ぜひフォローしてください!
他のセクションと同様に、コア ユーザーは ORM セクションをスキップできますが、ORM ユーザーが両方の観点からこれらのオブジェクトに慣れることが最善です。ここで説明する Table オブジェクトは、ORM を使用するときに、より間接的な (そして完全に Python 的な) 方法で宣言されますが、ORM の構成には依然として Table オブジェクトが存在します。

4-3-5-1. テーブルオブジェクトを使用したメタデータの設定

リレーショナル データベースを使用する場合、クエリを実行するデータベース内の基本的なデータ保持構造はテーブルと呼ばれます。SQLAlchemy では、データベースの「テーブル」は最終的には Table のような名前の Python オブジェクトによって表されます。

SQLAlchemy 式言語の使用を開始するには、対象となるすべてのデータベース テーブルを表す Table オブジェクトを構築する必要があります。テーブルは、Table コンストラクターを直接使用するか、ORM マッピング クラスを間接的に使用して、プログラムによって構築されます (「ORM 宣言形式を使用したテーブル メタデータの定義」で後述します)。リフレクションと呼ばれる、既存のデータベースから一部またはすべてのテーブル情報をロードするオプションもあります。

どのメソッドを使用する場合でも、常にコレクションから開始します。コレクションには、MetaData オブジェクトと呼ばれるテーブルが配置されます。このオブジェクトは本質的に、文字列名をキーとする一連の Table オブジェクトを格納する Python 辞書のラッパーです。ORM にはこのコレクションをどこから取得するかについていくつかのオプションが用意されていますが、以下に示すように、いつでもコレクションを直接作成することを選択できます。

from sqlalchemy import MetaData
metadata_obj = MetaData()

MetaData オブジェクトを取得したら、いくつかの Table オブジェクトを宣言できます。このチュートリアルは、古典的な SQLAlchemy チュートリアル モデルから開始します。このモデルには、Web サイト ユーザーなどの情報を格納する user_account というテーブルと、 user_account テーブルの行に関連付けられた電子メール アドレスを格納する関連テーブル アドレスがあります。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(100)),
)

上記の例では、データベース内の user_account テーブルを参照するコードを作成する場合、user_table Python 変数を使用してそれを参照します。

プログラム内で MetaData オブジェクトをいつ作成すればよいですか?
最も一般的なのは、アプリケーション全体の MetaData オブジェクトで、アプリケーション内の単一の場所 (通常は「models」または「dbschema」タイプのパッケージ) にモジュール レベルの変数として表されます。ORM 中心のレジストリまたは Declarative Base 基本クラスを通じて MetaData にアクセスすることも一般的であるため、同じ MetaData が ORM とコアで宣言された Table オブジェクト間で共有されます。
複数の MetaData コレクションが存在することもでき、Table オブジェクトは制限なく他のコレクション内の Table オブジェクトを参照できます。ただし、相互に関連する Table オブジェクトのグループの場合、実際には、宣言の観点から見ると、それらを単一の MetaData コレクション内で設定することや、適切な形式で発行される DDL (つまり、CREATE および DROP) ステートメントから設定する方がはるかに簡単です。順序と視点。

4-3-5-1-1. テーブルの構成要素

Python で記述されたテーブル構造は SQL の CREATE TABLE ステートメントに非常に似ていることがわかります。テーブル名で始まり、各列がリストされ、各列には名前とデータ型が含まれます。上記で使用したオブジェクトは次のとおりです。

  • テーブル - データベース テーブルを表し、それ自体を MetaData コレクションに割り当てます。

  • 列-データベーステーブル内の列を表し、それ自体をテーブルオブジェクトに割り当てます。通常、列には文字列名と型オブジェクトが含まれます。親 Table に関する Column オブジェクトのコレクションには、通常、Table.c にある連想配列を通じてアクセスします。

    user_table.c.name
    
    user_table.c.keys()
    
  • Integer、String - これらのクラスは SQL データ型を表し、インスタンス化が必要かどうかに関係なく Column に渡すことができます。上では、「name」列の長さ「30」を指定したいので、String(30) をインスタンス化します。ただし、「id」と「fullname」については指定しないので、クラス自体を送信できます。

参照:
メタデータを使用したデータベースの説明のメタデータ、テーブル、列に関するリファレンスおよび API ドキュメント。データ型のリファレンス ドキュメントは、SQL データ型オブジェクトにあります。

次のセクションでは、Table の基本機能の 1 つである、特定のデータベース接続で DDL を生成することについて説明します。ただし、最初に 2 番目のテーブルを宣言します。

4-3-5-1-2. 単純な制約の宣言

user_table の例の最初のカラムには、Column.primary_key パラメータが含まれています。これは、このカラムがテーブルの主キーであることを示す省略表現です。通常、主キー自体は暗黙的に宣言され、PrimaryKeyConstraint 構造によって表されます。これは、Table オブジェクトの Table.primary_key プロパティで確認できます。

user_table.primary_key

最も一般的に明示的に宣言される制約は、データベースの外部キー制約に対応するForeignKeyConstraint オブジェクトです。相互に関連するテーブルを宣言すると、SQLAlchemy はこれらの外部キー制約宣言の存在を使用して、それらを CREATE ステートメントでデータベースに送信するだけでなく、SQL 式の構築にも役立てます。

ターゲット テーブル上の 1 つの列のみに関係するForeignKeyConstraintは、通常、列レベルの省略記法を使用してForeignKeyオブジェクトを通じて宣言されます。以下では、ユーザー テーブルを参照する外部キー制約を持つ 2 番目のテーブル アドレスを宣言します。

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(100), nullable=False),
)

上記の表には 3 番目の制約もあります。これは、上記で Column.nullable パラメーターを使用して表現された SQL の「NOT NULL」制約です。

ヒント:
列定義でForeignKeyオブジェクトを使用する場合、列のデータ型を省略できます。これは、関連する列のデータ型(上記の例ではuser_account.id列の整数データ型)から自動的に推測されます。

次のセクションでは、ユーザー テーブルとアドレス テーブルの完全な DDL を発行して、完全な結果を確認します。

4-3-5-1-3. DDL をデータベースに送信する

データベース内の 2 つのデータベース テーブルを表すオブジェクト構造を構築します。ルート MetaData オブジェクトから始まり、次に 2 つの Table オブジェクトに至ります。各オブジェクトには <b2> オブジェクトと Constraint オブジェクトのコレクションが保持されます。このオブジェクト構造は、将来 Core と ORM で行うことのほとんどの中心となるでしょう。

この構造を使用して実行できる最初の便利な機能は、SQLite データベースに CREATE TABLE ステートメントまたは DDL を発行して、そこからデータを挿入してクエリできるようにすることです。これを行うために必要なツールはすべてすでに用意されており、MetaData で MetaData.create_all() メソッドを呼び出し、ターゲット データベースを参照するエンジンを送信します。

metadata_obj.create_all(engine)

出力結果は以下のようになります。

2023-09-16 20:27:24,738 INFO sqlalchemy.engine.Engine select sys_context( 'userenv', 'current_schema' ) from dual
2023-09-16 20:27:24,738 INFO sqlalchemy.engine.Engine [raw sql] {}
2023-09-16 20:27:24,746 INFO sqlalchemy.engine.Engine SELECT value FROM v$parameter WHERE name = 'compatible'
2023-09-16 20:27:24,746 INFO sqlalchemy.engine.Engine [raw sql] {}
2023-09-16 20:27:24,754 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 20:27:24,757 INFO sqlalchemy.engine.Engine SELECT tables_and_views.table_name 
FROM (SELECT a_tables.table_name AS table_name, a_tables.owner AS owner 
FROM all_tables a_tables UNION ALL SELECT a_views.view_name AS table_name, a_views.owner AS owner 
FROM all_views a_views) tables_and_views 
WHERE tables_and_views.table_name = :table_name AND tables_and_views.owner = :owner
2023-09-16 20:27:24,757 INFO sqlalchemy.engine.Engine [generated in 0.00048s] {'table_name': 'USER_ACCOUNT', 'owner': 'PDBADMIN'}
2023-09-16 20:27:24,760 INFO sqlalchemy.engine.Engine SELECT tables_and_views.table_name 
FROM (SELECT a_tables.table_name AS table_name, a_tables.owner AS owner 
FROM all_tables a_tables UNION ALL SELECT a_views.view_name AS table_name, a_views.owner AS owner 
FROM all_views a_views) tables_and_views 
WHERE tables_and_views.table_name = :table_name AND tables_and_views.owner = :owner
2023-09-16 20:27:24,760 INFO sqlalchemy.engine.Engine [cached since 0.003355s ago] {'table_name': 'ADDRESS', 'owner': 'PDBADMIN'}
2023-09-16 20:27:24,761 INFO sqlalchemy.engine.Engine 
CREATE TABLE address (
	id INTEGER NOT NULL, 
	user_id INTEGER NOT NULL, 
	email_address VARCHAR2(100 CHAR) NOT NULL, 
	PRIMARY KEY (id), 
	FOREIGN KEY(user_id) REFERENCES user_account (id)
)


2023-09-16 20:27:24,762 INFO sqlalchemy.engine.Engine [no key 0.00033s] {}
2023-09-16 20:27:24,772 INFO sqlalchemy.engine.Engine COMMIT

上記の DDL 作成プロセスには、CREATE を発行する前に各テーブルの存在をテストする SQLite 固有の PRAGMA ステートメントがいくつか含まれています。トランザクション DDL に対応するために、ステップの完全なシーケンスも BEGIN/COMMIT ペアに含まれています。

作成プロセスには、正しい順序で CREATE ステートメントを発行する責任もあります。上記では、FOREIGN KEY 制約は既存のユーザー テーブルに依存しているため、アドレス テーブルが最初に作成されます。より複雑な依存関係のシナリオでは、ALTER を使用して事後的に FOREIGN KEY 制約をテーブルに適用することもできます。

MetaData オブジェクトには MetaData.drop_all() メソッドもあります。このメソッドは、CREATE を発行してスキーマ要素を削除するのと逆の順序で DROP ステートメントを発行します。

多くの場合、移行ツールが適切です。
全体的に、MetaData の CREATE/DROP 機能は、テスト スイート、小規模および/または新しいアプリケーション、および有効期間の短いデータベースを使用するアプリケーションに役立ちます。ただし、アプリケーションのデータベース スキーマを長期的に管理する場合は、固定データベース スキーマを時間の経過とともに徐々に変更するプロセスを管理および調整できるため、スキーマ管理ツール (SQLAlchemy 上に構築された Alembic など) の方が良い選択肢になる可能性があります。適用などプログラムの設計が変更されます。

4-3-6. ORM宣言形式を使用したテーブルメタデータの定義

Table オブジェクトを作成する別の方法はありますか?

前の例は、Table オブジェクトの直接使用を示しています。これは、SQLAlchemy が SQL 式を構築するときに最終的にデータベース テーブルを参照する方法の基礎です。前述したように、SQLAlchemy ORM は、テーブル宣言プロセス (テーブル宣言と呼ばれる) の周りにファサードを提供します。宣言型テーブル プロシージャは、Table オブジェクトを構築するという前のセクションと同じ目標を達成しますが、途中で ORM マッピング クラス、または単に「マッピング クラス」と呼ばれるものも提供します。マッピング クラスは、ORM を使用する場合の SQL の最も一般的な基本単位であり、最新の SQLAlchemy ではコア中心としても非常に効果的に使用できます。

宣言型テーブルを使用すると、次のような利点があります。

  • より簡潔で Python スタイルの列定義を設定します。Python 型を使用して、データベースで使用される SQL 型を表すことができます。

  • 生成されたマッピング クラスは、多くの場合、Mypy や IDE 型チェッカーなどの静的分析ツールによって取得された PEP 484 型情報を保持する SQL 式を形成するために使用できます。

  • 永続化/オブジェクトのロード操作で使用されるテーブル メタデータと ORM マッピング クラスの両方の宣言を許可します。

このセクションでは、宣言テーブルを使用して構築された前のセクションと同じテーブル メタデータについて説明します。

ORM を使用する場合、テーブル メタデータを宣言するプロセスは、通常、マッピング クラスを宣言するプロセスと組み合わされます。マップされたクラスは、データベース テーブルの列にリンクされたプロパティを持つ、作成する Python クラスです。実装方法は多数ありますが、最も一般的なスタイルは宣言型と呼ばれるもので、ユーザー定義のクラスとテーブルのメタデータをすぐに宣言できます。

4-3-6-1. 宣言的基盤の確立

ORM を使用する場合、MetaData コレクションは引き続き存在しますが、それ自体は、多くの場合、宣言ベースと呼ばれる ORM 専用の構造に関連付けられます。新しい宣言ベースを取得する最も便利な方法は、SQLAlchemy DeclarativeBase クラスのサブクラスである新しいクラスを作成することです。

from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
    pass

上記の基本クラスは、宣言型基本クラスと呼ばれるものです。適切なクラスレベルのディレクティブと組み合わせた Base サブクラスとして新しいクラスを作成すると、それらはクラス作成時に新しい ORM マップされたクラスとして確立され、各クラスは通常 (ただし排他的ではありません) 特定の Table オブジェクトを参照します。

宣言ベースは、自動的に作成される MetaData コレクションを参照します (コレクションを外部に提供しないと仮定します)。この MetaData コレクションには、DeclarativeBase.metadata クラス レベル プロパティを通じてアクセスできます。新しいマッピング クラスを作成すると、それぞれがこの MetaData コレクション内のテーブルを参照します。

Base.metadata

宣言的基盤は、SQLAlchemy ORM の中心となる「マッパー構成」単位であるレジストリと呼ばれるコレクションも参照します。直接アクセスされることはほとんどありませんが、一連の ORM マッピング クラスがこのレジストリを通じて相互に調整されるため、このオブジェクトはマッパー構成プロセスの中心となります。MetaData の場合と同様に、宣言ベースもレジストリを作成します (これも独自のレジストリを渡すオプション付き)。これには DeclarativeBase.registry クラス変数を介してアクセスできます。

Base.registry

レジストリを使用してマップするその他の方法:
DeclarativeBase はクラスをマップする唯一の方法ではなく、最も一般的な方法です。レジストリは、デコレーターや命令型マッピング クラスのメソッドなど、他のマッパー構成モードも提供します。マッピング時の Python データ クラスの作成も完全にサポートされています。ORM マッピング クラスの設定に関するリファレンス ドキュメントにはすべてが記載されています。

4-3-6-2. マッピングクラスの宣言

Base クラスを確立した後、新しいクラス User および user_account とアドレス テーブルに基づいて ORM マッピング クラス 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]] = mapped_column(String(100))
    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] = mapped_column(String(100))
    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 および Address は現在 ORM マッピング クラスと呼ばれており、後述する ORM 永続化およびクエリ操作に使用できます。これらのクラスの詳細は次のとおりです。

  • 各クラスは、宣言的マッピング プロセスの一部として生成された Table オブジェクトを参照します。このオブジェクトには、 DeclarativeBase.tablename プロパティに文字列を割り当てることで名前が付けられますクラスを作成した後、生成された Table を DeclarativeBase.table プロパティから取得できます

  • 前述したように、この形式は宣言型テーブル構成と呼ばれます。いくつかの代替宣言スタイルの 1 つは、 Table オブジェクトを直接構築し、それをDeclarativeBase.tableに直接割り当てるものです。このスタイルは、コマンド リストを使用した宣言型と呼ばれます。

  • テーブル内の列を示すには、mapped_column() 構造を使用し、Mapped 型に基づく型注釈と組み合わせます。このオブジェクトは、Table の構築に使用される Column オブジェクトを生成します。

  • 単純なデータ型で他のオプションがない列の場合、int や str などの単純な Python 型を使用して Integer や String を表す、Mapped 型の注釈を個別に指定できます。宣言型マッピング中にカスタム Python 型が解釈される方法は非常に自由です。 ; 「注釈付き宣言テーブルの使用 (mapped_column() の型注釈付き形式)」および「カスタム バックグラウンド型マッピング」セクションを参照してください。

  • 列は、 Optional[] 型の注釈 (または同等の | None または Union[, None] ) の存在に基づいて、「nullable」または「non-nullable」として宣言できます。mapped_column.nullable パラメータは明示的に使用することもできます (注釈のオプションと一致する必要はありません)。

  • 明示的な型アノテーションの使用は完全にオプションです。アノテーションなしでmapped_column()を使用することもできます。この形式を使用する場合、必要に応じて、各mapped_column()構造でInteger、String、nullable=Falseなどのより明示的な型オブジェクトを使用します。

  • 2 つの追加プロパティ User.addresses と Address.user は、図に示すように、同様の注釈対応構成スタイルを持つ relationship() と呼ばれる異なるタイプのプロパティを定義します。relationship() 構造については、「ORM 関連オブジェクトの使用」で詳しく説明します。

  • 独自のメソッドを宣言しない場合、これらのクラスは自動的にinit () メソッドを取得します。このメソッドのデフォルト形式は、すべてのプロパティ名をオプションのキーワード引数として受け入れます。

    sandy = User(name="sandy", fullname="Sandy Cheeks")
    

    位置パラメータおよびデフォルトのキーワード値を持つパラメータを提供するフル機能のinit () メソッドを自動的に生成するには、宣言型データ クラス マッピングで導入されたデータ クラス機能を使用できます。もちろん、明示的なinit () メソッドの使用を選択することもできます。

  • repr () メソッドを追加して、読み取り可能な文字列出力を取得します。これらのメソッドはここでは必要ありません。init ()の場合と同様にrepr () メソッドはデータ クラスの機能を使用して自動的に生成できます。

4-3-6-3. ORMマッピングからデータベースへDDLを送信

ORM マッピング クラスは MetaData コレクションに含まれる Table オブジェクトを参照するため、宣言ベースで DDL を発行するには、データベースへの DDL の送信で前述したのと同じプロセスが使用されます。私たちの場合、SQLite データベースにユーザー テーブルとアドレス テーブルを生成しました。まだ行っていない場合は、DeclarativeBase.metadata からコレクションにアクセスすることで、ORM 宣言基本クラスに関連付けられた MetaData を使用して自由にこれを行うことができます。プロパティを設定してから、以前と同様に MetaData.create_all() を使用します。この場合、PRAGMA ステートメントは実行されますが、新しいテーブルはすでに存在していることが判明したため、生成されません。

Base.metadata.create_all(engine)

出力結果は以下のようになります。

2023-09-16 20:38:53,836 INFO sqlalchemy.engine.Engine select sys_context( 'userenv', 'current_schema' ) from dual
2023-09-16 20:38:53,837 INFO sqlalchemy.engine.Engine [raw sql] {}
2023-09-16 20:38:53,841 INFO sqlalchemy.engine.Engine SELECT value FROM v$parameter WHERE name = 'compatible'
2023-09-16 20:38:53,842 INFO sqlalchemy.engine.Engine [raw sql] {}
2023-09-16 20:38:53,849 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 20:38:53,852 INFO sqlalchemy.engine.Engine SELECT tables_and_views.table_name 
FROM (SELECT a_tables.table_name AS table_name, a_tables.owner AS owner 
FROM all_tables a_tables UNION ALL SELECT a_views.view_name AS table_name, a_views.owner AS owner 
FROM all_views a_views) tables_and_views 
WHERE tables_and_views.table_name = :table_name AND tables_and_views.owner = :owner
2023-09-16 20:38:53,853 INFO sqlalchemy.engine.Engine [generated in 0.00053s] {'table_name': 'USER_ACCOUNT', 'owner': 'PDBADMIN'}
2023-09-16 20:38:53,855 INFO sqlalchemy.engine.Engine SELECT tables_and_views.table_name 
FROM (SELECT a_tables.table_name AS table_name, a_tables.owner AS owner 
FROM all_tables a_tables UNION ALL SELECT a_views.view_name AS table_name, a_views.owner AS owner 
FROM all_views a_views) tables_and_views 
WHERE tables_and_views.table_name = :table_name AND tables_and_views.owner = :owner
2023-09-16 20:38:53,855 INFO sqlalchemy.engine.Engine [cached since 0.003387s ago] {'table_name': 'ADDRESS', 'owner': 'PDBADMIN'}
2023-09-16 20:38:53,857 INFO sqlalchemy.engine.Engine 
CREATE TABLE user_account (
	id INTEGER NOT NULL, 
	name VARCHAR2(30 CHAR) NOT NULL, 
	fullname VARCHAR2(100 CHAR), 
	PRIMARY KEY (id)
)


2023-09-16 20:38:53,857 INFO sqlalchemy.engine.Engine [no key 0.00032s] {}
2023-09-16 20:38:53,868 INFO sqlalchemy.engine.Engine 
CREATE TABLE address (
	id INTEGER NOT NULL, 
	email_address VARCHAR2(100 CHAR) NOT NULL, 
	user_id INTEGER, 
	PRIMARY KEY (id), 
	FOREIGN KEY(user_id) REFERENCES user_account (id)
)


2023-09-16 20:38:53,869 INFO sqlalchemy.engine.Engine [no key 0.00051s] {}
2023-09-16 20:38:53,877 INFO sqlalchemy.engine.Engine COMMIT

4-3-7. 表面反射

このセクションでは、テーブル リフレクションの関連トピック、または既存のデータベースからテーブル オブジェクトを自動的に生成する方法のみを簡単に紹介します。チュートリアルの読者でクエリの作成を続けたい場合は、このセクションをスキップしても構いません。

テーブル メタデータの操作に関するセクションの締めくくりとして、このセクションの冒頭で説明した別の操作であるテーブル リフレクションについて説明します。テーブル リフレクションとは、データベースの現在のステータスを読み取り、テーブルと関連オブジェクトを生成するプロセスを指します。前のセクションでは、Python で Table オブジェクトを宣言しました。その後、データベースに DDL を発行してそのようなスキーマを生成するオプションがあり、リフレクション プロセスではこれら 2 つの手順の逆を実行して、既存のデータベース構造から Python データを生成します。データベース内のスキーマを表します。

ヒント:
既存のデータベースで SQLAlchemy を使用する場合、リフレクションを使用する必要はありません。Python の SQLAlchemy アプリケーションでは、構造が既存のデータベースに対応するようにすべてのメタデータを明示的に宣言するのが一般的です。また、メタデータ構造には、テーブル、列、その他の制約や、ローカル アプリケーションの実行に必要のない既存のデータベースからの構築を含める必要もありません。

リフレクションの例として、このドキュメントの前半で手動で作成した some_table オブジェクトを表す新しい Table オブジェクトを作成します。これを行う方法はたくさんありますが、最も基本的な方法は、テーブルの名前とそれが属する MetaData コレクションを指定して Table オブジェクトを構築し、個別の Column オブジェクトと Constraint オブジェクトを指定する代わりに、Table.autoload_with を使用することです。ターゲット エンジンに渡すパラメータ:

some_table = Table("some_table", metadata_obj, autoload_with=engine)

出力結果は以下のようになります。

2023-09-16 20:42:00,309 INFO sqlalchemy.engine.Engine SELECT a_tab_cols.table_name, a_tab_cols.column_name, a_tab_cols.data_type, a_tab_cols.char_length, a_tab_cols.data_precision, a_tab_cols.data_scale, a_tab_cols.nullable, a_tab_cols.data_default, a_col_comments.comments, a_tab_cols.virtual_column, a_tab_cols.default_on_null, CASE WHEN (a_tab_identity_cols.table_name IS NULL) THEN NULL ELSE a_tab_identity_cols.generation_type || :generation_type_1 || a_tab_identity_cols.identity_options END AS identity_options 
FROM all_tab_cols a_tab_cols LEFT OUTER JOIN all_col_comments a_col_comments ON a_tab_cols.table_name = a_col_comments.table_name AND a_tab_cols.column_name = a_col_comments.column_name AND a_tab_cols.owner = a_col_comments.owner LEFT OUTER JOIN all_tab_identity_cols a_tab_identity_cols ON a_tab_cols.table_name = a_tab_identity_cols.table_name AND a_tab_cols.column_name = a_tab_identity_cols.column_name AND a_tab_cols.owner = a_tab_identity_cols.owner 
WHERE a_tab_cols.table_name IN (:all_objects_1) AND a_tab_cols.hidden_column = :hidden_column_1 AND a_tab_cols.owner = :owner_1 ORDER BY a_tab_cols.table_name, a_tab_cols.column_id
2023-09-16 20:42:00,310 INFO sqlalchemy.engine.Engine [generated in 0.00106s] {'generation_type_1': ',', 'hidden_column_1': 'NO', 'owner_1': 'PDBADMIN', 'all_objects_1': 'SOME_TABLE'}
2023-09-16 20:42:00,464 INFO sqlalchemy.engine.Engine SELECT a_objects.object_name 
FROM all_objects a_objects 
WHERE a_objects.owner = :owner_1 AND a_objects.object_type IN (:object_type_1_1, :object_type_1_2) AND a_objects.object_name IN (:filter_names_1)
2023-09-16 20:42:00,464 INFO sqlalchemy.engine.Engine [generated in 0.00053s] {'owner_1': 'PDBADMIN', 'object_type_1_1': 'TABLE', 'object_type_1_2': 'VIEW', 'filter_names_1': 'SOME_TABLE'}
2023-09-16 20:42:00,505 INFO sqlalchemy.engine.Engine SELECT a_constraints.table_name, a_constraints.constraint_type, a_constraints.constraint_name, local.column_name AS local_column, remote.table_name AS remote_table, remote.column_name AS remote_column, remote.owner AS remote_owner, a_constraints.search_condition, a_constraints.delete_rule 
FROM all_constraints a_constraints JOIN all_cons_columns local ON local.owner = a_constraints.owner AND a_constraints.constraint_name = local.constraint_name LEFT OUTER JOIN all_cons_columns remote ON a_constraints.r_owner = remote.owner AND a_constraints.r_constraint_name = remote.constraint_name AND (remote.position IS NULL OR local.position = remote.position) 
WHERE a_constraints.owner = :owner_1 AND a_constraints.table_name IN (:all_objects_1) AND a_constraints.constraint_type IN (:constraint_type_1_1, :constraint_type_1_2, :constraint_type_1_3, :constraint_type_1_4) ORDER BY a_constraints.constraint_name, local.position
2023-09-16 20:42:00,505 INFO sqlalchemy.engine.Engine [generated in 0.00052s] {'owner_1': 'PDBADMIN', 'all_objects_1': 'SOME_TABLE', 'constraint_type_1_1': 'R', 'constraint_type_1_2': 'P', 'constraint_type_1_3': 'U', 'constraint_type_1_4': 'C'}
2023-09-16 20:42:01,043 INFO sqlalchemy.engine.Engine SELECT a_ind_columns.table_name, a_ind_columns.index_name, a_ind_columns.column_name, a_indexes.index_type, a_indexes.uniqueness, a_indexes.compression, a_indexes.prefix_length, a_ind_columns.descend, a_ind_expressions.column_expression 
FROM all_ind_columns a_ind_columns JOIN all_indexes a_indexes ON a_ind_columns.index_name = a_indexes.index_name AND a_ind_columns.index_owner = a_indexes.owner LEFT OUTER JOIN all_ind_expressions a_ind_expressions ON a_ind_expressions.index_name = a_ind_columns.index_name AND a_ind_expressions.index_owner = a_ind_columns.index_owner AND a_ind_expressions.column_position = a_ind_columns.column_position 
WHERE a_indexes.table_owner = :table_owner_1 AND a_indexes.table_name IN (:all_objects_1) ORDER BY a_ind_columns.index_name, a_ind_columns.column_position
2023-09-16 20:42:01,044 INFO sqlalchemy.engine.Engine [generated in 0.00056s] {'table_owner_1': 'PDBADMIN', 'all_objects_1': 'SOME_TABLE'}
2023-09-16 20:42:01,221 INFO sqlalchemy.engine.Engine SELECT tables_and_views.table_name, tables_and_views.comments 
FROM (SELECT a_tab_comments.table_name AS table_name, a_tab_comments.comments AS comments 
FROM all_tab_comments a_tab_comments 
WHERE a_tab_comments.owner = :owner_1 AND a_tab_comments.table_name NOT LIKE :table_name_1 UNION ALL SELECT a_mview_comments.mview_name AS table_name, a_mview_comments.comments AS comments 
FROM all_mview_comments a_mview_comments 
WHERE a_mview_comments.owner = :owner_2 AND a_mview_comments.mview_name NOT LIKE :mview_name_1) tables_and_views 
WHERE tables_and_views.table_name IN (:filter_names_1)
2023-09-16 20:42:01,222 INFO sqlalchemy.engine.Engine [generated in 0.00059s] {'owner_1': 'PDBADMIN', 'table_name_1': 'BIN$%', 'owner_2': 'PDBADMIN', 'mview_name_1': 'BIN$%', 'filter_names_1': 'SOME_TABLE'}
2023-09-16 20:42:01,324 INFO sqlalchemy.engine.Engine SELECT a_tables.table_name, a_tables.compression, a_tables.compress_for 
FROM all_tables a_tables 
WHERE a_tables.owner = :owner_1 AND a_tables.table_name IN (:filter_names_1)
2023-09-16 20:42:01,324 INFO sqlalchemy.engine.Engine [generated in 0.00093s] {'owner_1': 'PDBADMIN', 'filter_names_1': 'SOME_TABLE'}
2023-09-16 20:42:01,537 INFO sqlalchemy.engine.Engine SELECT a_views.view_name 
FROM all_views a_views 
WHERE a_views.owner = :owner_1
2023-09-16 20:42:01,538 INFO sqlalchemy.engine.Engine [generated in 0.00075s] {'owner_1': 'PDBADMIN'}
2023-09-16 20:42:01,559 INFO sqlalchemy.engine.Engine ROLLBACK

プロセスの最後に、some_table オブジェクトには、テーブル内に存在する Column オブジェクトに関する情報が含まれており、このオブジェクトは明示的に宣言した Table と同じ方法で使用されることがわかります。

some_table

出力結果は以下のようになります。

Table('some_table', MetaData(), Column('x', INTEGER(), table=<some_table>), Column('y', INTEGER(), table=<some_table>), schema=None)

これで、2 つのテーブルと、接続や ORM セッション経由で使用できるコアおよび ORM テーブル指向の構成を備えた Oracle データベースが準備できました。次のセクションでは、これらの構造を使用してデータを作成、操作、選択する方法について説明します。

4-4. データの加工

「トランザクションと DBAPI の使用」では、Python DBAPI とそのトランザクション状態を操作する方法の基本を学びました。次に、「データベース メタデータの操作」では、SQLAlchemy でデータベース テーブル、列、制約を表すために MetaData と関連オブジェクトを使用する方法を学びました。このセクションでは、上記の 2 つの概念を組み合わせて、リレーショナル データベース内のデータを作成、選択、操作します。データベースとのやり取りは、バックグラウンドで自動コミットを使用するようにデータベース ドライバーを設定している場合でも、常にトランザクションの形式になります。

このセクションの構成要素は次のとおりです。

  • INSERT ステートメントの使用 - データベースにデータを入力するために、コアとなる Insert コンストラクトを導入し、デモしました。次のセクション「データ操作での ORM の使用」では、ORM の観点から INSERT について説明します。

  • SELECT ステートメントの使用 - このセクションでは、SQLAlchemy で最も一般的に使用されるオブジェクトである Select コンストラクトについて詳しく説明します。Select コンストラクトは、ここで説明する 2 つの使用例である、コア アプリケーションと ORM 中心のアプリケーション用の SELECT ステートメントを生成します。他の ORM の使用例については、「クエリでのリレーションシップの使用」セクションおよび「ORM クエリ ガイド」で後述します。

  • UPDATE ステートメントと DELETE ステートメントの使用 - データの INSERT と SELECTion を完了するため、このセクションでは、中心的な観点から Update 構造と Delete 構造の使用法について説明します。ORM 固有の UPDATE および DELETE については、「ORM によるデータ操作」セクションで同様に説明します。

4-4-1. INSERT文の使い方

Core を使用し、バッチ操作に ORM を使用する場合、SQL INSERT ステートメントは insert() 関数を使用して直接生成されます。この関数は、テーブルに新しいデータを追加する INSERT SQL ステートメントを表す Insert の新しいインスタンスを生成します。

このセクションでは、単一の SQL INSERT ステートメントを生成してテーブルに新しい行を追加する中心的な方法について詳しく説明します。ORM を使用する場合、通常は、この上で実行される作業ユニットと呼ばれる別のツールを使用します。このツールは、同時に多数の INSERT ステートメントを自動的に生成します。ただし、ORM が実行されているとしても、Core がデータの作成と操作をどのように処理するかを理解することは役に立ちます。さらに、ORM は、一括/複数行 INSERT、upsert、UPDATE、および DELETE と呼ばれる機能を使用して、INSERT を直接操作することをサポートします。

通常の作業単位モードを使用して ORM を介して行を INSERT する方法に直接ジャンプするには、「ORM 作業単位モードを使用した行の挿入」を参照してください。

4-4-1-1. insert() SQL式の構造

Insert の簡単な例は、ターゲット テーブルと VALUES 句の両方を示しています。

from sqlalchemy import insert
stmt = insert(user_table).values(id=3, name="spongebob", fullname="Spongebob Squarepants")

上記の stmt 変数は、Insert のインスタンスです。ほとんどの SQL 式は、生成されたものの一般的な形式を確認する方法として、その場で文字列化できます。

print(stmt)

出力結果は以下のようになります。

INSERT INTO user_account (id, name, fullname) VALUES (:id, :name, :fullname)

文字列化された形式は、ステートメントのデータベース固有の文字列 SQL 表現を含むオブジェクトのコンパイル済み形式を生成することによって作成されます。このオブジェクトは、ClauseElement.compile() メソッドを使用して直接取得できます。

compiled = stmt.compile()

前述のパラメーターの送信で示したように、Insert コンストラクトは「パラメーター化された」コンストラクトの例です。名前とフルネームのバインド パラメーターを確認するには、これらのパラメーターを Compiled コンストラクトからも入手できます。

compiled.params

出力結果は以下のようになります。

{'id': 3, 'name': 'spongebob', 'fullname': 'Spongebob Squarepants'}
4-4-1-2. ステートメントの実行

このステートメントを呼び出すことで、user_table に行を挿入できます。INSERT SQL とバンドルされたパラメーターは SQL ログで確認できます。

with engine.connect() as conn:
    result = conn.execute(stmt)
    conn.commit()

出力結果は以下のようになります。

2023-09-16 21:09:58,974 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 21:09:58,974 INFO sqlalchemy.engine.Engine INSERT INTO user_account (id, name, fullname) VALUES (:id, :name, :fullname)
2023-09-16 21:09:58,974 INFO sqlalchemy.engine.Engine [generated in 0.00099s] {'id': 3, 'name': 'spongebob', 'fullname': 'Spongebob Squarepants'}
2023-09-16 21:09:58,977 INFO sqlalchemy.engine.Engine COMMIT

上記の単純な形式では、INSERT ステートメントは行を返しません。1 行だけが挿入された場合、通常、その行の INSERT 中に生成された列レベルのデフォルト値 (ほとんどの場合は整数) に関する情報を返す機能が含まれます。主キーの値。上記の場合、SQLite データベースの最初の行は、通常、最初の整数の主キー値として 1 を返します。これは、CursorResult.inserted_primary_key アクセサーを使用して取得できます。

result.inserted_primary_key

出力結果は以下のようになります。

(3,)

ヒント:
主キーには複数の列が含まれる場合があるため、CursorResult.inserted_primary_key はタプルを返します。これは複合主キーと呼ばれます。CursorResult.inserted_primary_key は、「cursor.lastrowid」型の値だけでなく、挿入したばかりのレコードの完全な主キーを常に含むことを目的としており、「自動インクリメント」が使用されているかどうかに関係なく値が設定されることも意図されています。タプルである完全な主キーを表現するためです。

4-4-1-3. INSERT は通常、「values」句を自動的に生成します

上記の例では、Insert.values() メソッドを使用して、SQL INSERT ステートメントの VALUES 句を明示的に作成します。実際に Insert.values() を使用せず、単に「空」ステートメントを出力すると、テーブル内の各列に対して INSERT が取得されます。

print(insert(user_table))

出力結果は以下のようになります。

INSERT INTO user_account (id, name, fullname) VALUES (:id, :name, :fullname)

まだ Insert.values() を呼び出していない Insert コンストラクトを取得し、出力する代わりに実行すると、ステートメントは Connection.execute() メソッドに渡した引数に基づいて文字列にコンパイルされ、渡された引数と同じ引数が含まれます 関連列。これは、実際には、明示的に VALUES 句を入力せずに Insert を使用して行を挿入する一般的な方法です。次の例は、パラメーター リストを使用して一度に実行される 2 列の INSERT ステートメントを示しています。

with engine.connect() as conn:
    result = conn.execute(
        insert(user_table),
        [
            {"id": 4, "name": "sandy", "fullname": "Sandy Cheeks"},
            {"id": 5, "name": "patrick", "fullname": "Patrick Star"},
        ],
    )
    conn.commit()

出力結果は以下のようになります。

2023-09-16 21:14:38,806 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 21:14:38,807 INFO sqlalchemy.engine.Engine INSERT INTO user_account (id, name, fullname) VALUES (:id, :name, :fullname)
2023-09-16 21:14:38,807 INFO sqlalchemy.engine.Engine [generated in 0.00084s] [{'id': 4, 'name': 'sandy', 'fullname': 'Sandy Cheeks'}, {'id': 5, 'name': 'patrick', 'fullname': 'Patrick Star'}]
2023-09-16 21:14:38,816 INFO sqlalchemy.engine.Engine COMMIT

上記の実行は、「複数のパラメーターの送信」で最初に説明した「executemany」形式をとりますが、text() コンストラクトを使用する場合とは異なり、SQL を詳しく記述する必要はありません。ディクショナリまたはディクショナリのリストを Insert コンストラクトとともに Connection.execute() メソッドに渡すことにより、Connection は、渡された列名が Insert コンストラクトの VALUES 句で自動的に表現されるようにします。

Deep Alchemy:
皆さん、こんにちは。Deep Alchemy の初版へようこそ。左側の人々は錬金術師と呼ばれていますが、とんがり帽子がめくれていないことから、彼らが魔法使いではないことがわかります。Alchemist は、通常はより高度な内容やトリッキーで、通常は必要のない内容について説明しますが、何らかの理由で、SQLAlchemy で何ができるかを知っておくべきだと彼らは考えています。
このリリースでは、興味深いデータを address_table に含めるために、Insert.values() メソッドを明示的に使用しながらパラメーターから生成された追加の値を含める方法のより高度な例を示します。次のセクションで説明する select() 構造を使用してスカラー サブクエリを構築し、明示的なバインド パラメータ名を使用して、bindparam() を使用して構築されるサブクエリで使用されるパラメータを設定します。
これは、user_table 操作から主キー識別子をアプリケーションに取得せずに、関連する行を追加できるようにするための、より深い錬金術です。ほとんどの錬金術師は、ORM を使用してこのようなことを処理します。

from sqlalchemy import select, bindparam
scalar_subq = (
   select(user_table.c.id)
   .where(user_table.c.name == bindparam("username"))
   .scalar_subquery()
)

with engine.connect() as conn:
   result = conn.execute(
       insert(address_table).values(user_id=scalar_subq),
       [
           {
               "username": "spongebob",
               "email_address": "[email protected]",
               "id": 3
           },
           {"username": "sandy", "email_address": "[email protected]", "id": 4},
           {"username": "sandy", "email_address": "[email protected]", "id": 5},
       ],
   )
   conn.commit()

このようにして、テーブルにさらに興味深いデータが得られます。これは次のセクションで使用します。

出力結果は以下のようになります。

2023-09-16 23:18:46,390 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 23:18:46,390 INFO sqlalchemy.engine.Engine INSERT INTO address (id, user_id, >email_address) VALUES (:id, (SELECT user_account.id 
FROM user_account 
WHERE user_account.name = :username), :email_address)
2023-09-16 23:18:46,391 INFO sqlalchemy.engine.Engine [generated in 0.00096s] [{'id': 3, >'username': 'spongebob', 'email_address': '[email protected]'}, {'id': 4, 'username': >'sandy', 'email_address': '[email protected]'}, {'id': 5, 'username': 'sandy', 'email_address': >'[email protected]'}]
2023-09-16 23:18:46,400 INFO sqlalchemy.engine.Engine COMMIT
4-4-1-4. 挿入...戻る

サポートされているバックエンドの RETURNING 句を自動的に使用して、最後に挿入された主キー値とサーバーのデフォルト値を取得します。ただし、RETURNING 句は、Insert.returning() メソッドを使用して明示的に指定することもできます。この場合、ステートメントの実行時に返される Result オブジェクトには、取得できる行が含まれます。

insert_stmt = insert(address_table).returning(
    address_table.c.id, address_table.c.email_address
)
print(insert_stmt)

出力結果は以下のようになります。

INSERT INTO address (id, user_id, email_address) VALUES (:id, :user_id, :email_address) RETURNING address.id, address.email_address

次の例に示すように、Insert.from_select() と組み合わせて使用​​することもできます。これは、INSERT…FROM SELECT で説明した例に基づいています。

select_stmt = select(user_table.c.id, user_table.c.name + "@aol.com")
insert_stmt = insert(address_table).from_select(
    ["user_id", "email_address"], select_stmt
)
print(insert_stmt.returning(address_table.c.id, address_table.c.email_address))

出力結果は以下のようになります。

INSERT INTO address (user_id, email_address) SELECT user_account.id, user_account.name || :name_1 AS anon_1 
FROM user_account RETURNING address.id, address.email_address

ヒント:
UPDATE ステートメントと DELETE ステートメントは RETURNING 機能もサポートしています。これについては、このチュートリアルで後ほど説明します。
INSERT ステートメントの場合、RETURNING 関数は、単一行ステートメントと、一度に複数行を INSERT するステートメントの両方に使用できます。RETURNING を使用した複数行 INSERT のサポートは方言固有ですが、SQLAlchemy に含まれる RETURNING 対応の方言はすべてサポートされています。この機能の背景情報については、INSERT ステートメントの「複数の値の挿入の動作」セクションを参照してください。
ORM は、RETURNING の有無にかかわらず、バッチ INSERT もサポートします。参考ドキュメントについては、「ORM Bulk INSERTステートメント」を参照してください。

4-4-1-5. 選択範囲から挿入...

Insert のあまり使用されていない機能ですが、完全を期すために、Insert.from_select() メソッドを使用して SELECT から直接行を取得する INSERT に組み込むことができる Insert コンストラクトを次に示します。このメソッドは、select() 構造 (次のセクションで説明します) と、実際の INSERT に配置される列名のリストを受け入れます。次の例では、user_account テーブルの行から派生した行がアドレス テーブルに追加され、各ユーザーに無料の電子メール アドレス aol.com が与えられます。

select_stmt = select(user_table.c.id, user_table.c.name + "@aol.com")
insert_stmt = insert(address_table).from_select(
    ["user_id", "email_address"], select_stmt
)
print(insert_stmt)

出力結果は以下のようになります。

INSERT INTO address (user_id, email_address) SELECT user_account.id, user_account.name || :name_1 AS anon_1 
FROM user_account

この構造は、実際にクライアントからデータを取得して再送信することなく、データベースの別の部分から新しい行セットにデータを直接コピーしたい場合に使用できます。

参照: SQL Expression API ドキュメントの
Insert -

4-4-2. SELECT文の使い方

Core および ORM の場合、select() 関数はすべての SELECT クエリに使用される Select 構造を生成します。Core の Connection.execute() や ORM の Session.execute() などのメソッドに渡され、現在のトランザクションで SELECT ステートメントを発行し、返された Result オブジェクトを通じて結果行を取得します。

ORM リーダー - ここの内容はコアと ORM の使用法に等しく適用され、基本的な ORM バリアントの使用例がここで説明されています。ただし、利用可能な ORM 固有の機能は他にもあります。これらについては、「ORM クエリ ガイド」に記載されています。

4-4-2-1. select() SQL式の構造

select() コンストラクトは、各メソッドがオブジェクトに対してさらに多くの状態を構築するビルド メソッドを使用して、insert() と同じ方法でステートメントを構築します。他の SQL 構造と同様に、インプレースで文字列化できます。

from sqlalchemy import select
stmt = select(user_table).where(user_table.c.name == "spongebob")
print(stmt)

出力結果は以下のようになります。

SELECT user_account.id, user_account.name, user_account.fullname 
FROM user_account 
WHERE user_account.name = :name_1

また、他のすべてのステートメント レベルの SQL 構造と同様に、ステートメントを実際に実行するには、ステートメントを実行メソッドに渡します。SELECT ステートメントは行を返すため、いつでも結果オブジェクトを反復処理して Row オブジェクトを取得できます。

with engine.connect() as conn:
    for row in conn.execute(stmt):
        print(row)

出力結果は以下のようになります。

2023-09-16 21:30:37,111 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 21:30:37,112 INFO sqlalchemy.engine.Engine SELECT user_account.id, user_account.name, user_account.fullname 
FROM user_account 
WHERE user_account.name = :name_1
2023-09-16 21:30:37,112 INFO sqlalchemy.engine.Engine [generated in 0.00111s] {'name_1': 'spongebob'}
(3, 'spongebob', 'Spongebob Squarepants')
2023-09-16 21:30:37,117 INFO sqlalchemy.engine.Engine ROLLBACK

ORM を使用するとき、特に ORM エンティティ構成に対して select() コンストラクトを使用するときは、セッションで Session.execute() メソッドを使用してそれを実行する必要があります。このメソッドを使用して、引き続き Row オブジェクトを取得します。その結果、行には User クラスのインスタンスなどの完全なエンティティを含めることができ、各行内で個別の要素として配置されます。

stmt = select(User).where(User.name == "spongebob")
with Session(engine) as session:
    for row in session.execute(stmt):
        print(row)

出力結果は以下のようになります。

2023-09-16 21:31:55,654 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 21:31:55,656 INFO sqlalchemy.engine.Engine SELECT user_account.id, user_account.name, user_account.fullname 
FROM user_account 
WHERE user_account.name = :name_1
2023-09-16 21:31:55,656 INFO sqlalchemy.engine.Engine [generated in 0.00042s] {'name_1': 'spongebob'}
(User(id=3, name='spongebob', fullname='Spongebob Squarepants'),)
2023-09-16 21:31:55,658 INFO sqlalchemy.engine.Engine ROLLBACK

ORM クラスを含むテーブルからの select()
これらの例で生成された SQL は、select(user_table) を呼び出しても select(User) を呼び出しても同じように見えますが、より一般的なケースでは、ORM-マップされたクラスは同じコンテンツをレンダリングする場合があります。テーブル以外のタイプの「選択可能」にマップします。ORM エンティティの select() は、結果として ORM マップのインスタンスを返す必要があることも示しますが、Table オブジェクトから SELECT する場合はそうではありません。

次のセクションでは、SELECT 構造について詳しく説明します。

4-4-2-2. COLUMNS句とFROM句の設定

select() 関数は、任意の数の Column 式や Table 式、およびさまざまな互換性のあるオブジェクトを表す位置要素を受け入れます。これらのオブジェクトは、選択対象の SQL 式のリストに解析され、結果セットの列として返されます。これらの要素は、より単純な場合に、渡された列とテーブルのような式から推論される FROM 句を作成するために使用することもできます。

print(select(user_table))

出力結果は以下のようになります。

SELECT user_account.id, user_account.name, user_account.fullname 
FROM user_account

コア メソッドを使用して個々の列から SELECT する場合、Column オブジェクトは Table.c アクセサーからアクセスでき、直接送信できます。FROM 句は、これらの列で表されるすべての Table および他の FromClause オブジェクトのセットであると推論されます。

print(select(user_table.c.name, user_table.c.fullname))

出力結果は以下のようになります。

SELECT user_account.name, user_account.fullname 
FROM user_account

あるいは、任意の FromClause (Table など) の FromClause.c コレクションを使用する場合、タプルを使用して select() に複数の列文字列名を指定できます。

print(select(user_table.c["name", "fullname"]))

出力結果は以下のようになります。

SELECT user_account.name, user_account.fullname 
FROM user_account

あるいは、任意の FromClause (Table など) の FromClause.c コレクションを使用する場合、タプルを使用して select() に複数の列文字列名を指定できます。

print(select(user_table.c["name", "fullname"]))

出力結果は以下のようになります。

SELECT user_account.name, user_account.fullname 
FROM user_account

バージョン 2.0 の新機能: .FromClause.c:attr コレクションにタプル アクセサー機能を追加しました。

4-4-2-3. ORM エンティティと列の選択

User クラスやその列マッピング プロパティ ( User.name など) などの ORM エンティティも、テーブルと列を表す SQL 式言語システムに参加します。以下は、User エンティティからの SELECT の例を示しています。これは、user_table を直接使用している場合と同じ方法でレンダリングされます。

print(select(User))

出力結果は以下のようになります。

SELECT user_account.id, user_account.name, user_account.fullname 
FROM user_account

ORM Session.execute() メソッドを使用して上記のステートメントを実行する場合、user_table と同様に完全なエンティティ (例: User) から選択すると、エンティティ自体が各行の単一要素として返されます。つまり、上記のステートメントから行を取得する場合、取得するコンテンツ リストには User エンティティのみがあるため、User クラスのインスタンスを含む要素を 1 つだけ含む Row オブジェクトを返します。

row = session.execute(select(User)).first()
row

出力結果は以下のようになります。

2023-09-16 22:31:59,892 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 22:31:59,893 INFO sqlalchemy.engine.Engine SELECT user_account.id, user_account.name, user_account.fullname 
FROM user_account
2023-09-16 22:31:59,893 INFO sqlalchemy.engine.Engine [generated in 0.00040s] {}
(User(id=3, name='spongebob', fullname='Spongebob Squarepants'),)

上記の行には、User エンティティを表す要素が 1 つだけあります。

row[0]

出力結果は以下のようになります。

User(id=3, name='spongebob', fullname='Spongebob Squarepants')

上記と同じ結果を得るために強く推奨される便利な方法は、Session.scalars() メソッドを使用してステートメントを直接実行することです。このメソッドは、一度に各行の最初の「列」を提供する ScalarResult オブジェクトを返します。ユーザークラスの例:

user = session.scalars(select(User)).first()
user

出力結果は以下のようになります。

2023-09-16 22:33:26,428 INFO sqlalchemy.engine.Engine SELECT user_account.id, user_account.name, user_account.fullname 
FROM user_account
2023-09-16 22:33:26,429 INFO sqlalchemy.engine.Engine [cached since 86.54s ago] {}
User(id=3, name='spongebob', fullname='Spongebob Squarepants')

あるいは、クラス バインディング プロパティを使用して、ORM エンティティの個々の列を結果行のさまざまな要素として選択することもできます。それらが select() などの構造に渡されると、列として解析されるか、各プロパティで表されます。 その他の SQL式:

print(select(User.name, User.fullname))

出力結果は以下のようになります。

SELECT user_account.name, user_account.fullname 
FROM user_account

Session.execute() を使用してこのステートメントを呼び出すと、値ごとに個別の要素を含む行を受け取ります。各要素は個別の列または他の SQL 式に対応します。

row = session.execute(select(User.name, User.fullname)).first()
row

出力結果は以下のようになります。

2023-09-16 22:35:27,303 INFO sqlalchemy.engine.Engine SELECT user_account.name, user_account.fullname 
FROM user_account
2023-09-16 22:35:27,303 INFO sqlalchemy.engine.Engine [generated in 0.00065s] {}
('spongebob', 'Spongebob Squarepants')

これらのメソッドは混合することもできます。以下に示すように、User エンティティの name 属性を行の最初の要素として選択し、それを 2 番目の要素の完全な Address エンティティと組み合わせます。

session.execute(
    select(User.name, Address).where(User.id == Address.user_id).order_by(Address.id)
).all()

出力結果は以下のようになります。

2023-09-16 22:36:15,285 INFO sqlalchemy.engine.Engine SELECT user_account.name, address.id, address.email_address, address.user_id 
FROM user_account, address 
WHERE user_account.id = address.user_id ORDER BY address.id
2023-09-16 22:36:15,285 INFO sqlalchemy.engine.Engine [generated in 0.00083s] {}
[('spongebob', Address(id=3, email_address='[email protected]')),
 ('sandy', Address(id=4, email_address='[email protected]')),
 ('sandy', Address(id=5, email_address='[email protected]'))]

ORM エンティティと列を選択する方法、および行を変換する一般的な方法については、「ORM エンティティとプロパティの選択」で詳しく説明します。

4-4-2-4. ラベル付きSQL式から選択

ColumnElement.label() メソッドは、ORM プロパティで使用できる同じ名前のメソッドと同様に、列または式に SQL ラベルを提供し、結果セット内で特定の名前を持つことができます。これは、結果行の任意の SQL 式を名前で参照するときに役立ちます。

from sqlalchemy import func, cast
stmt = select(
    ("Username: " + user_table.c.name).label("username"),
).order_by(user_table.c.name)
with engine.connect() as conn:
    for row in conn.execute(stmt):
        print(f"{row.username}")

出力結果は以下のようになります。

2023-09-16 22:37:50,176 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 22:37:50,177 INFO sqlalchemy.engine.Engine SELECT :name_1 || user_account.name AS username 
FROM user_account ORDER BY user_account.name
2023-09-16 22:37:50,177 INFO sqlalchemy.engine.Engine [generated in 0.00085s] {'name_1': 'Username: '}
Username: patrick
Username: sandy
Username: spongebob
2023-09-16 22:37:50,180 INFO sqlalchemy.engine.Engine ROLLBACK
4-4-2-5. テキスト列式を使用した選択

select() 関数を使用して Select オブジェクトを構築する場合、通常はテーブル メタデータを使用して定義された一連の Table オブジェクトと Column オブジェクトを渡します。または、ORM を使用する場合は、テーブル列を表す ORM マッピングを送信することもあります。属性。ただし、ステートメント内に任意の SQL ブロック (定数文字列式など)、または文字通りに記述する方が高速な任意の SQL を作成する必要がある場合があります。

DBAPI で導入されたトランザクションと text() コンストラクトを使用すると、実際には Select コンストラクトに直接埋め込むことができます。たとえば、以下ではハードコードされた文字列リテラル「何らかのフレーズ」を作成し、それを SELECT ステートメントに埋め込みます。

from sqlalchemy import text
stmt = select(text("'some phrase'"), user_table.c.name).order_by(user_table.c.name)
with engine.connect() as conn:
    print(conn.execute(stmt).all())

出力結果は以下のようになります。

2023-09-16 22:38:53,506 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 22:38:53,506 INFO sqlalchemy.engine.Engine SELECT 'some phrase', user_account.name 
FROM user_account ORDER BY user_account.name
2023-09-16 22:38:53,507 INFO sqlalchemy.engine.Engine [generated in 0.00093s] {}
[('some phrase', 'patrick'), ('some phrase', 'sandy'), ('some phrase', 'spongebob')]
2023-09-16 22:38:53,510 INFO sqlalchemy.engine.Engine ROLLBACK

text() 構造はリテラル SQL フレーズを挿入するためにほとんどの場所で使用できますが、実際に扱うことが多いのは、単一の列式を表す各テキスト単位です。この一般的なケースでは、 literal_column() コンストラクトを使用して、テキスト フラグメントからさらに多くの機能を取得できます。このオブジェクトは text() に似ていますが、任意の SQL の形式を表すのではなく、サブクエリやその他の式でタグ付けして参照できる単一の「列」を明示的に表す点が異なります。

from sqlalchemy import literal_column
stmt = select(literal_column("'some phrase'").label("p"), user_table.c.name).order_by(
    user_table.c.name
)
with engine.connect() as conn:
    for row in conn.execute(stmt):
        print(f"{row.p}, {row.name}")

出力結果は以下のようになります。

2023-09-16 22:39:31,739 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 22:39:31,740 INFO sqlalchemy.engine.Engine SELECT 'some phrase' AS p, user_account.name 
FROM user_account ORDER BY user_account.name
2023-09-16 22:39:31,740 INFO sqlalchemy.engine.Engine [generated in 0.00102s] {}
some phrase, patrick
some phrase, sandy
some phrase, spongebob
2023-09-16 22:39:31,744 INFO sqlalchemy.engine.Engine ROLLBACK

どちらの場合も、text() または literal_column() を使用するときは、リテラル値ではなく、構文的な SQL 式を記述していることに注意してください。したがって、レンダリングしたい SQL に必要な引用や構文を含める必要があります。

4-4-2-6. WHERE 子句

SQLAlchemy を使用すると、 name = 'squidward' や user_id > 10 などの Column および類似のオブジェクトを含む標準の Python 演算子を使用して SQL 式を作成できます。ブール式の場合、ほとんどの Python 演算子 (==、!=、<、>= など) は、通常のブール True/False 値の代わりに新しい SQL 式オブジェクトを生成します。

print(user_table.c.name == "squidward")

出力結果は以下のようになります。

user_account.name = :name_1
print(address_table.c.user_id > 10)

出力結果は以下のようになります。

address.user_id > :user_id_1

次のような式を使用して、結果オブジェクトを Select.where() メソッドに渡すことで WHERE 句を生成できます。

print(select(user_table).where(user_table.c.name == "squidward"))

出力結果は以下のようになります。

SELECT user_account.id, user_account.name, user_account.fullname 
FROM user_account 
WHERE user_account.name = :name_1

AND で接続された複数の式を生成するには、Select.where() メソッドを何度でも呼び出すことができます。

print(
    select(address_table.c.email_address)
    .where(user_table.c.name == "squidward")
    .where(address_table.c.user_id == user_table.c.id)
)

出力結果は以下のようになります。

SELECT address.email_address 
FROM address, user_account 
WHERE user_account.name = :name_1 AND address.user_id = user_account.id

Select.where() を 1 回呼び出すと、同じ効果を持つ複数の式も受け入れられます。

print(
    select(address_table.c.email_address).where(
        user_table.c.name == "squidward",
        address_table.c.user_id == user_table.c.id,
    )
)

出力結果は以下のようになります。

SELECT address.email_address 
FROM address, user_account 
WHERE user_account.name = :name_1 AND address.user_id = user_account.id

次の図の ORM エンティティに示すように、「AND」と「OR」の結合は両方とも、and_() 関数と or_() 関数を使用して直接使用できます。

from sqlalchemy import and_, or_
print(
    select(Address.email_address).where(
        and_(
            or_(User.name == "squidward", User.name == "sandy"),
            Address.user_id == User.id,
        )
    )
)

出力結果は以下のようになります。

SELECT address.email_address 
FROM address, user_account 
WHERE (user_account.name = :name_1 OR user_account.name = :name_2) AND address.user_id = user_account.id

単一エンティティとの単純な「同等性」比較には、列キーまたは ORM プロパティ名と一致するキーワード引数を受け入れる Select.filter_by() と呼ばれる一般的なメソッドもあります。左端の FROM 句または最後に追加されたエンティティに基づいてフィルタリングされます。

print(select(User).filter_by(name="spongebob", fullname="Spongebob Squarepants"))

出力結果は以下のようになります。

SELECT user_account.id, user_account.name, user_account.fullname 
FROM user_account 
WHERE user_account.name = :name_1 AND user_account.fullname = :fullname_1
4-4-2-7. 明示的な FROM 句と JOIN

前に述べたように、FROM 句は通常、columns 句および Select のその他の要素に設定した式に基づいて推論されます。

COLUMNS 句で特定のテーブルの単一の列を設定すると、そのテーブルが FROM 句にも追加されます。

print(select(user_table.c.name))

出力結果は以下のようになります。

SELECT user_account.name 
FROM user_account

2 つのテーブルから列を配置する場合は、カンマで区切られた FROM 句を取得します。

print(select(user_table.c.name, address_table.c.email_address))

出力結果は以下のようになります。

SELECT user_account.name, address.email_address 
FROM user_account, address

これら 2 つのテーブルを結合するには、通常、Select で 2 つの方法のいずれかを使用します。1 つ目は Select.join_from() メソッドで、JOIN の左側と右側を明示的に指定できます。

print(
    select(user_table.c.name, address_table.c.email_address).join_from(
        user_table, address_table
    )
)

出力結果は以下のようになります。

SELECT user_account.name, address.email_address 
FROM user_account JOIN address ON user_account.id = address.user_id

もう 1 つは Select.join() メソッドで、JOIN の右側のみを示し、左側を推論します。

print(select(user_table.c.name, address_table.c.email_address).join(address_table))

出力結果は以下のようになります。

SELECT user_account.name, address.email_address 
FROM user_account JOIN address ON user_account.id = address.user_id

ON 句が推論される:
Select.join_from() または Select.join() を使用すると、単純な外部キーの場合、結合の ON 句も推論されることがあります。これについては次のセクションで詳しく説明します。

要素が columns 句から希望どおりに推論されない場合、要素を FROM 句に明示的に追加するオプションもあります。これを実現するには、以下に示すように、Select.select_from() メソッドを使用します。FROM 句の最初の要素として user_table を構築し、Select.join() を使用して address_table を構築します。

print(select(address_table.c.email_address).select_from(user_table).join(address_table))

出力結果は以下のようになります。

SELECT address.email_address 
FROM user_account JOIN address ON user_account.id = address.user_id

Select.select_from() を使用する別の例は、columns 句に FROM 句に提供するのに十分な情報がない場合です。たとえば、一般的な SQL 式 count(*) から SELECT するには、sqlalchemy.sql.expression.func という名前の SQLAlchemy 要素を使用して SQL count() 関数を生成します。

from sqlalchemy import func
print(select(func.count("*")).select_from(user_table))

出力結果は以下のようになります。

from sqlalchemy import func
print(select(func.count("*")).select_from(user_table))

出力結果は以下のようになります。

SELECT count(:count_2) AS count_1 
FROM user_account

参照:
『ORM クエリ ガイド』の結合における左端の FROM 句の設定には、Select.select_from() と Select.join() の相互作用に関する追加の例とコメントが含まれています。

4-4-2-8. Set ON句

前の JOIN の例は、Select 構造が 2 つのテーブルを結合し、ON 句を自動的に生成できることを示しています。これらの例でこれが発生するのは、user_table および address_table テーブル オブジェクトに、この ON 句を形成する単一のForeignKeyConstraint 定義が含まれているためです。

接続の左右のターゲットにそのような制約がない場合、または複数の制約がある場合は、ON 句を直接指定する必要があります。Select.join() と Select.join_from() はどちらも ON 句への追加引数を受け入れます。これは、WHERE 句で見たのと同じ SQL 式メカニズムを使用して宣言されます。

print(
    select(address_table.c.email_address)
    .select_from(user_table)
    .join(address_table, user_table.c.id == address_table.c.user_id)
)

出力結果は以下のようになります。

SELECT address.email_address 
FROM user_account JOIN address ON user_account.id = address.user_id

ORM のヒント - 前のセクションで宣言したマッピング クラスでマッピング セットを設定するのと同じように、relationship() を使用して構築された ORM エンティティを使用するときに ON 句を生成する別の方法があります。これは、リレーショナル結合の使用で詳しく説明されているトピック全体です。

4-4-2-9. 外部接続と完全接続

Select.join() メソッドと Select.join_from() メソッドはどちらもキーワード引数 Select.join.isouter と Select.join.full を受け入れ、それぞれ LEFT OUTER JOIN と FULL OUTER JOIN をレンダリングします。

print(select(user_table).join(address_table, isouter=True))

出力結果は以下のようになります。

SELECT user_account.id, user_account.name, user_account.fullname 
FROM user_account LEFT OUTER JOIN address ON user_account.id = address.user_id
print(select(user_table).join(address_table, full=True))

出力結果は以下のようになります。

SELECT user_account.id, user_account.name, user_account.fullname 
FROM user_account FULL OUTER JOIN address ON user_account.id = address.user_id

.join(…, isouter=True) を使用するのと同等のメソッド Select.outerjoin() もあります。

SQLには「RIGHT OUTER JOIN」もあります。SQLAlchemy はそれを直接レンダリングせず、テーブルの順序を逆にし、「LEFT OUTER JOIN」を使用します。

4-4-2-10。ORDER BY、GROUP BY、HAVING

SELECT SQL ステートメントには、選択された行を指定された順序で返す ORDER BY という句が含まれています。

GROUP BY 句は ORDER BY 句と同様に構成されており、その目的は、選択した行を、集計関数を呼び出すことができる特定のグループに分割することです。HAVING 句は通常、GROUP BY とともに使用され、グループ内で使用される集計関数に適用される点を除いて、WHERE 句と形式が似ています。

ご注文は

ORDER BY 句は、通常は列または同様のオブジェクトに基づく SQL 式構造に基づいて構築されます。Select.order_by() メソッドは、位置ごとに次の 1 つ以上の式を受け入れます。

print(select(user_table).order_by(user_table.c.name))

出力結果は以下のようになります。

SELECT user_account.id, user_account.name, user_account.fullname 
FROM user_account ORDER BY user_account.name

昇順/降順は、ORM バインドされたプロパティにも存在する ColumnElement.asc() および ColumnElement.desc() 修飾子を介して使用できます。

print(select(User).order_by(User.fullname.desc()))

出力結果は以下のようになります。

SELECT user_account.id, user_account.name, user_account.fullname 
FROM user_account ORDER BY user_account.fullname DESC

上記のステートメントは、user_account.fullname 列によって降順にソートされた行を生成します。

GROUP BY / HAVING 集計関数を使用し

SQL では、集計関数を使用すると、複数の行の列式を集計して 1 つの結果を生成できます。例には、カウント、平均の計算、一連の値の最大値または最小値の検索などが含まれます。

SQLAlchemy は、func と呼ばれる名前空間を使用してオープンな方法で SQL 関数を提供します。これは、特定の SQL 関数の名前 (任意の名前を付けることができます) と関数に渡される 0 個以上の引数 (他のすべての場合と同様、SQL 式の構築) が指定された場合に、Function の新しいインスタンスを作成する特別なコンストラクター オブジェクトです。 。たとえば、user_account.id 列に対して SQL COUNT() 関数をレンダリングするには、count() 名を呼び出します。

from sqlalchemy import func
count_fn = func.count(user_table.c.id)
print(count_fn)

出力結果は以下のようになります。

count(user_account.id)

SQL 関数については、このチュートリアルの後半の「SQL 関数の使用」で詳しく説明します。

GROUP BY 句は、行をグループに分割し、各グループに個別に集計関数を適用できるため、SQL で集計関数を使用する場合に重要です。SELECT ステートメントの COLUMNS 句で非集計列が要求される場合、SQL では、これらの列すべてが GROUP BY 句の対象となり、主キーに基づいて直接的または間接的に関連付けられる必要があります。HAVING 句は、直接の行の内容ではなく集計値に基づいて行をフィルタリングする点を除いて、WHERE 句と同様の方法で使用されます。

SQLAlchemy は、Select.group_by() メソッドと Select.having() メソッドを使用してこれら 2 つの句を提供します。以下では、複数のアドレスを持つユーザーのユーザー名フィールドとアドレス数を選択する方法を説明します。

with engine.connect() as conn:
    result = conn.execute(
        select(User.name, func.count(Address.id).label("count"))
        .join(Address)
        .group_by(User.name)
        .having(func.count(Address.id) > 1)
    )
    print(result.all())

出力結果は以下のようになります。

2023-09-16 22:57:09,039 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 22:57:09,040 INFO sqlalchemy.engine.Engine SELECT user_account.name, count(address.id) AS count 
FROM user_account JOIN address ON user_account.id = address.user_id GROUP BY user_account.name 
HAVING count(address.id) > :count_1
2023-09-16 22:57:09,040 INFO sqlalchemy.engine.Engine [generated in 0.00087s] {'count_1': 1}
[('sandy', 2)]

2023-09-16 22:57:09,051 INFO sqlalchemy.engine.Engine ROLLBACK

タグによる並べ替えまたはグループ化

特に一部のデータベース バックエンドで重要なテクニックは、ORDER BY または GROUP BY 句で式を再宣言することなく、代わりに列名を使用して、Columns 句で宣言された式を ORDER BY または GROUP BY できることです。またはCOLUMNS句のタグ名。このフォームを使用するには、名前の文字列リテラルを Select.order_by() または Select.group_by() メソッドに渡します。渡されたテキストは直接レンダリングされず、代わりに、columns 句の式に指定された名前がコンテキスト内の式名としてレンダリングされ、一致するものが見つからない場合はエラーが発生します。単項修飾子 asc() および desc() も次の形式で使用できます。

from sqlalchemy import func, desc
stmt = (
    select(Address.user_id, func.count(Address.id).label("num_addresses"))
    .group_by("user_id")
    .order_by("user_id", desc("num_addresses"))
)
print(stmt)

出力結果は以下のようになります。

SELECT address.user_id, count(address.id) AS num_addresses 
FROM address GROUP BY address.user_id ORDER BY address.user_id, num_addresses DESC
4-4-2-11. エイリアスの使用

複数のテーブルから選択して結合を使用しているため、ステートメントの FROM 句で同じテーブルを複数回参照する必要がある状況がすぐに発生します。これは、テーブルまたはサブクエリにステートメント内で参照できる代替名を与えるための構文である SQL エイリアスを使用して行います。

SQLAlchemy 式言語では、これらの「名前」は、FromClause.alias() メソッドを使用してコアで構築される Alias コンストラクトと呼ばれる FromClause オブジェクトによって表されます。Alias コンストラクトは、Table コンストラクトと同様に、Alias.c コレクション内の Column オブジェクトの名前空間を持ちます。たとえば、次の SELECT ステートメントは、すべての一意のユーザー名のペアを返します。

user_alias_1 = user_table.alias()
user_alias_2 = user_table.alias()
print(
    select(user_alias_1.c.name, user_alias_2.c.name).join_from(
        user_alias_1, user_alias_2, user_alias_1.c.id > user_alias_2.c.id
    )
)

出力結果は以下のようになります。

SELECT user_account_1.name, user_account_2.name AS name_1 
FROM user_account AS user_account_1 JOIN user_account AS user_account_2 ON user_account_1.id > user_account_2.id

ORM エンティティ エイリアス

FromClause.alias() メソッドに相当する ORM は ORM aliased() 関数であり、User や Address などのエンティティに適用できます。これにより、ORM 機能を維持しながら、元のマップされた Table オブジェクトに対応する Alias オブジェクトが内部的に生成されます。次の SELECT は、2 つの特定の電子メール アドレスを含むすべてのオブジェクトを User エンティティから選択します。

from sqlalchemy.orm import aliased
address_alias_1 = aliased(Address)
address_alias_2 = aliased(Address)
print(
    select(User)
    .join_from(User, address_alias_1)
    .where(address_alias_1.email_address == "[email protected]")
    .join_from(User, address_alias_2)
    .where(address_alias_2.email_address == "[email protected]")
)

出力結果は以下のようになります。

SELECT user_account.id, user_account.name, user_account.fullname 
FROM user_account JOIN address AS address_1 ON user_account.id = address_1.user_id JOIN address AS address_2 ON user_account.id = address_2.user_id 
WHERE address_1.email_address = :email_address_1 AND address_2.email_address = :email_address_2

「ON 句の設定」で説明したように、ORM は、 relationship() 構造を使用して接続する別の方法を提供します。エイリアスを使用する上記の例は、リレーションシップを使用して接続するエイリアス ターゲット間で relationship() を使用して示します。

4-4-2-12. サブクエリと CTE

SQL のサブクエリは、括弧内に示され、それを囲むステートメント (通常は SELECT ステートメントですが、必ずしもそうである必要はありません) のコンテキスト内に配置される SELECT ステートメントです。

このセクションでは、いわゆる「非スカラー」サブクエリについて説明します。これらは通常、囲む SELECT の FROM 句に配置されます。また、サブクエリと同様の方法で使用されますが、追加の機能が含まれる共通テーブル式 (CTE) についても紹介します。

SQLAlchemy は、サブクエリを表すために Subquery オブジェクトを使用し、CTE を表すために CTE を使用します。通常、それぞれ Select.subquery() メソッドと Select.cte() メソッドから取得されます。どちらのオブジェクトも、より大きな select() 構造内の FROM 要素として使用できます。

アドレス テーブルから行の集計数を選択するサブクエリを構築できます (集計関数と GROUP BY は、GROUP BY / HAVING を使用する集計関数で以前に紹介しました)。

subq = (
    select(func.count(address_table.c.id).label("count"), address_table.c.user_id)
    .group_by(address_table.c.user_id)
    .subquery()
)

サブクエリを別の Select または他のステートメントに埋め込まずに単独で文字列化すると、括弧のない単純な SELECT ステートメントが生成されます。

print(subq)

出力結果は以下のようになります。

SELECT count(address.id) AS count, address.user_id 
FROM address GROUP BY address.user_id

Subquery オブジェクトは、選択する列の Subquery.c 名前空間が含まれることを除いて、他の FROM オブジェクト (Table など) と同様に動作します。この名前空間を使用して、user_id 列とカスタム タグ数式を参照できます。

print(select(subq.c.user_id, subq.c.count))

出力結果は以下のようになります。

SELECT anon_1.user_id, anon_1.count 
FROM (SELECT count(address.id) AS count, address.user_id AS user_id 
FROM address GROUP BY address.user_id) AS anon_1

subq オブジェクトに含まれる行を選択することで、そのオブジェクトを、データを user_account テーブルに結合するより大きな Select に適用できます。

stmt = select(user_table.c.name, user_table.c.fullname, subq.c.count).join_from(
    user_table, subq
)

print(stmt)

出力結果は以下のようになります。

SELECT user_account.name, user_account.fullname, anon_1.count 
FROM user_account JOIN (SELECT count(address.id) AS count, address.user_id AS user_id 
FROM address GROUP BY address.user_id) AS anon_1 ON user_account.id = anon_1.user_id

user_account から address に参加するには、Select.join_from() メソッドを使用します。前述したように、この結合の ON 句は再び外部キー制約から推論されます。SQL サブクエリ自体に制約がない場合でも、SQLAlchemy は、subq.c.user_id 列が address_table.c.user_id 列から派生していると判断することで、subq.c.user_id 列で表現された制約を操作できます。この列は外部キー関係を表しています。次に、c.id 列を使用して ON 句が生成されます。

共通テーブル式 (CTE)

SQLAlchemy の CTE 構造は、実際には Subquery 構造と同じ方法で使用されます。代わりに Select.cte() を使用するように Select.subquery() メソッドの呼び出しを変更すると、同じ方法で結果オブジェクトを FROM 要素として使用できますが、レンダリングされる SQL は一般的なテーブル式とは大きく異なります。構文:

subq = (
    select(func.count(address_table.c.id).label("count"), address_table.c.user_id)
    .group_by(address_table.c.user_id)
    .cte()
)

stmt = select(user_table.c.name, user_table.c.fullname, subq.c.count).join_from(
    user_table, subq
)

print(stmt)

出力結果は以下のようになります。

WITH anon_1 AS 
(SELECT count(address.id) AS count, address.user_id AS user_id 
FROM address GROUP BY address.user_id)
 SELECT user_account.name, user_account.fullname, anon_1.count 
FROM user_account JOIN anon_1 ON user_account.id = anon_1.user_id

CTE 構造は「再帰的」スタイルで使用することもでき、より複雑な場合には、INSERT、UPDATE、または DELETE ステートメントの RETURNING 句で構成されます。CTE の docstring には、これらの追加モードに関する詳細情報が含まれています。

どちらの場合も、サブクエリと CTE は SQL レベルで「匿名」名を使用して名前が付けられます。Python コードでは、これらの名前を指定する必要はまったくありません。サブクエリまたは CTE インスタンスのオブジェクト識別子は、レンダリング時にオブジェクトの構文識別子として機能します。SQL でレンダリングされる名前は、Select.subquery() または Select.cte() メソッドの最初のパラメーターとして渡すことによって提供できます。

関連項目:
Select.subquery() - サブクエリの詳細
Select.cte() - DML での RECURSIVE と CTE の使用方法を含む CTE の例

ORM エンティティ サブクエリ/CTE

ORM では、aliased() コンストラクトを使用して、ORM エンティティ (User クラスや Address クラスなど) を行ソースを表す FromClause 概念と組み合わせることができます。ORM エンティティ エイリアスに関する前のセクションでは、aliased() を使用して、マップされたクラスをそのマップされたテーブルのエイリアスに関連付ける方法を説明しました。ここでは、aliased() がサブクエリと Select コンストラクトに対して生成された CTE に対して同じ操作を実行し、最終的に同じマップからテーブルを導出することを示します。

以下は、サブクエリ構成に aliased() を適用して、ORM エンティティを行から抽出できるようにする例です。結果には、一連の User オブジェクトと Address オブジェクトが表示されます。各 Address オブジェクトのデータは、最終的にはテーブルから直接ではなく、アドレスに対するサブクエリ テーブルから取得されます。

subq = select(Address).where(~Address.email_address.like("%@aol.com")).subquery()
address_subq = aliased(Address, subq)
stmt = (
    select(User, address_subq)
    .join_from(User, address_subq)
    .order_by(User.id, address_subq.id)
)
with Session(engine) as session:
    for user, address in session.execute(stmt):
        print(f"{user} {address}")

出力結果は以下のようになります。

2023-09-16 23:06:09,195 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 23:06:09,198 INFO sqlalchemy.engine.Engine SELECT user_account.id, user_account.name, user_account.fullname, anon_1.id AS id_1, anon_1.email_address, anon_1.user_id 
FROM user_account JOIN (SELECT address.id AS id, address.email_address AS email_address, address.user_id AS user_id 
FROM address 
WHERE address.email_address NOT LIKE :email_address_1) anon_1 ON user_account.id = anon_1.user_id ORDER BY user_account.id, anon_1.id
2023-09-16 23:06:09,198 INFO sqlalchemy.engine.Engine [generated in 0.00071s] {'email_address_1': '%@aol.com'}
2023-09-16 23:06:09,204 INFO sqlalchemy.engine.Engine ROLLBACK

次に別の例を示します。これは、CTE 構造を使用することを除いてまったく同じです。

cte_obj = select(Address).where(~Address.email_address.like("%@aol.com")).cte()
address_cte = aliased(Address, cte_obj)
stmt = (
    select(User, address_cte)
    .join_from(User, address_cte)
    .order_by(User.id, address_cte.id)
)
with Session(engine) as session:
    for user, address in session.execute(stmt):
        print(f"{user} {address}")

出力結果は以下のようになります。

2023-09-16 23:06:50,019 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 23:06:50,021 INFO sqlalchemy.engine.Engine WITH anon_1 AS 
(SELECT address.id AS id, address.email_address AS email_address, address.user_id AS user_id 
FROM address 
WHERE address.email_address NOT LIKE :email_address_1)
 SELECT user_account.id, user_account.name, user_account.fullname, anon_1.id AS id_1, anon_1.email_address, anon_1.user_id 
FROM user_account JOIN anon_1 ON user_account.id = anon_1.user_id ORDER BY user_account.id, anon_1.id
2023-09-16 23:06:50,021 INFO sqlalchemy.engine.Engine [generated in 0.00045s] {'email_address_1': '%@aol.com'}
2023-09-16 23:06:50,025 INFO sqlalchemy.engine.Engine ROLLBACK
4-4-2-13. スカラーサブクエリと相関サブクエリ

スカラー サブクエリは、正確に 0 または 1 行と 1 列を返すサブクエリです。このサブクエリは、それを囲んでいる SELECT ステートメントの COLUMNS または WHERE 句で使用されます。通常のサブクエリとは異なり、FROM 句では使用されません。相関サブクエリは、それを囲む SELECT ステートメント内のテーブルを参照するスカラー サブクエリです。

SQLAlchemy は、FromClause 階層にある Subquery 構造によって表される通常のサブクエリとは対照的に、ScalarSelect 構造を使用して、ColumnElement 式階層の一部であるスカラー サブクエリを表します。

スカラー サブクエリは、GROUP BY / HAVING を使用した集計関数で以前に説明した集計関数でよく (必ずではありませんが) 使用されます。スカラー サブクエリは、次に示すように Select.scalar_subquery() メソッドを使用して明示的に指定されます。それ自体が文字列化されると、それはデフォルトの文字列形式となり、2 つのテーブルから選択する通常の SELECT ステートメントとしてレンダリングされます。

subq = (
    select(func.count(address_table.c.id))
    .where(user_table.c.id == address_table.c.user_id)
    .scalar_subquery()
)
print(subq)

出力結果は以下のようになります。

(SELECT count(address.id) AS count_1 
FROM address, user_account 
WHERE user_account.id = address.user_id)

上記の subq オブジェクトは、他の列式と同様に使用できるため、ColumnElement SQL 式階層に属します。

print(subq == 5)

出力結果は以下のようになります。

(SELECT count(address.id) AS count_1 
FROM address, user_account 
WHERE user_account.id = address.user_id) = :param_1

スカラー サブクエリ自体は、FROM 句で文字列化されると user_account と address の両方をレンダリングしますが、user_account テーブルは、サブクエリの FROM 句ではレンダリングされない意味を処理する囲み select() 構造に埋め込まれると、自動的に関連付けられます。

stmt = select(user_table.c.name, subq.label("address_count"))
print(stmt)

出力結果は以下のようになります。

SELECT user_account.name, (SELECT count(address.id) AS count_1 
FROM address 
WHERE user_account.id = address.user_id) AS address_count 
FROM user_account

通常、単純な相関サブクエリは、必要な処理を適切に実行します。ただし、依存関係が不明瞭な場合は、SQLAlchemy によって、より明確にする必要があることが通知されます。

stmt = (
    select(
        user_table.c.name,
        address_table.c.email_address,
        subq.label("address_count"),
    )
    .join_from(user_table, address_table)
    .order_by(user_table.c.id, address_table.c.id)
)
print(stmt)

出力結果は以下のようになります。

InvalidRequestError: Select statement '<sqlalchemy.sql.selectable.Select object at 0x00000246786AEF50>' returned no FROM clauses due to auto-correlation; specify correlate(<tables>) to control correlation manually.

user_table が相関を求めているオブジェクトであることを指定するには、ScalarSelect.correlate() メソッドまたは ScalarSelect.correlate_Except() メソッドを使用してこれを指定します。

subq = (
    select(func.count(address_table.c.id))
    .where(user_table.c.id == address_table.c.user_id)
    .scalar_subquery()
    .correlate(user_table)
)

その後、ステートメントは他の列と同様に、その列のデータを返すことができます。

with engine.connect() as conn:
    result = conn.execute(
        select(
            user_table.c.name,
            address_table.c.email_address,
            subq.label("address_count"),
        )
        .join_from(user_table, address_table)
        .order_by(user_table.c.id, address_table.c.id)
    )
    print(result.all())

出力結果は以下のようになります。

2023-09-16 23:13:51,030 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-09-16 23:13:51,030 INFO sqlalchemy.engine.Engine SELECT user_account.name, address.email_address, (SELECT count(address.id) AS count_1 
FROM address 
WHERE user_account.id = address.user_id) AS address_count 
FROM user_account JOIN address ON user_account.id = address.user_id ORDER BY user_account.id, address.id
2023-09-16 23:13:51,031 INFO sqlalchemy.engine.Engine [generated in 0.00117s] {}
[('spongebob', '[email protected]', 1), ('sandy', '[email protected]', 2), ('sandy', '[email protected]', 2)]

2023-09-16 23:13:51,039 INFO sqlalchemy.engine.Engine ROLLBACK

つづく!

https://docs.sqlalchemy.org/en/20/tutorial/data_select.html => 横方向の相関

おすすめ

転載: blog.csdn.net/engchina/article/details/132922339