当サイトはPR広告を利用しています。

【理解度チェックテスト付き】SQLAlchemyの基本的な使い方

2019年8月21日

SQLAlchemyの基本的な使い方を解説していきます。進め方は公式のチュートリアルに即しています。

この記事ではSQLAlchemyについて説明したのち、インストール手順と基本的な使い方を解説します。

最後に理解度チェッククイズも準備しているので、もし解けない問題があれば再度説明を読み返してみてください。チェックテストの問題が全て解けるようになればSQLAlchemy中級者を名乗っても良いでしょう。

著者環境

OS : Ubuntu 18.04.2 LTS
python : python 3.6.8
SQLAlchemy : 1.3.7

SQLAlchemyとは

SQLAlchemyとはPythonを使ってDB操作を簡単に行うためのツールです。公式サイトでは以下の説明がされています。

The Python SQL Toolkit and Object Relational Mapper
SQLAlchemy is the Python SQL toolkit and Object Relational Mapper that gives application developers the full power and flexibility of SQL.
It provides a full suite of well known enterprise-level persistence patterns, designed for efficient and high-performing database access, adapted into a simple and Pythonic domain language.

https://www.sqlalchemy.org/

SQLAlchemyを使うことで、PythonのクラスとDB内のテーブルを関連づけることができます。

その結果、インスタンス生成をするように新規レコードを登録したり、テーブルに関連づけられたクラスから生成されたインスタンスが持つ変数に値を代入することで、レコードの情報を更新することが出来ます。

ちなみにこのようにクラスとテーブルを関連づける機能のことをORM(Object-relational mapping)と呼びます。

インストール方法

SQLAlchemyのインストール方法を説明します。

もしpip3コマンドが利用できる環境であればのコマンドを実行してインストールします。pip3はpythonコード内で利用できるライブラリをインストールしたりアンインストールしたりするために使うコマンドです。

$ pip3 install sqlalchemy

pipをまだインストールしていない環境であれば、以下のコマンドを実行してpipを利用できる状態にしてからSQLAlchemyをインストールします。

# pip3コマンドを使うために必要なものをインストール
$ sudo apt update
$ sudo apt install python3-pip

# SQLAlchemyのインストール
$ pip3 install sqlalchemy

前準備

SQLAlchemyを利用してテーブル操作を行うためには以下のステップを行う必要があります。

  1. DBへの接続設定
  2. DB操作用クラスの作成
  3. DB操作用クラスとDBの紐付け
  4. セッションの作成

上記を行なって初めてSQLAlchemyでやりたいことが実現できます。

順番に説明していきます。

DBへの接続設定

まず最初に「どのデータベース」に「どうやって接続するか」の設定を行います。
その設定内容を保持したものはエンジンと呼ばれ、このエンジンが全てのDB操作の足がかりとなります。

注意: >>>で始まっている行はpythonコマンドラインから実行していることを表しています。コマンドライン実行をするためにはターミナル上で「python3」と入力後、Enterキーを押下してください。するとpythonをコマンドライン実行できるようになります。

# エンジン作成のための関数をimport
>>> from sqlalchemy import create_engine

# エンジンオブジェクトの作成
>>> engine = create_engine('sqlite:///:memory:', echo=True)

create_engine関数のパラメータについて解説します。

第一引数

create_engineの第一引数では「どのデータベース」に「どうやって接続するか」を指定します。今回の場合はUbuntuに標準で入っている"sqlite"というデータベース管理システムを使用します。sqlite:///:memory:と指定した場合にはsqliteのインメモリデータベース内にアクセスすることになります。

インメモリデータベースはデータをディスクに保存するのではなくメモリ上に記録します。そのためファイル実行が完了した時や、pythonコマンドラインを抜けたりなど一連の処理が終わったら記録していたデータは消えてしまいます。ディスク容量を食うことがないので、簡単に操作を試したり検証時に便利です。

sqliteではなくpostgresqlで作成したDBに対して接続したい場合は以下のようなパラメータでエンジンを作ります。

>>> engine = create_engine('postgresql://username:password@hostname/database_name', echo=True)

hostnameの箇所は「IPアドレス:port番号」という書き方でもOKです。postgresqlにアクセスする場合はPsycopg2というパッケージが必要です。もしpostgresqlを利用したいという方は以下の記事に従ってインストールしてから先に進んでください。

第2引数(echo)

echoの値がTrueの場合、SQLAlchemyが発行したSQL文がログとして出力されるようになります。最初のうちはTrueにして、SQLAlchemyによってどのようなSQL文が発行されているのか確認しましょう。

テーブル作成

今回のチュートリアル用にテーブルを作成します。せっかくなのでSQLAlchemyを利用してテーブルを作成してみます。

テーブルを作成する方法は2つあります。1つがMetaDataを使う方法、もう一つがdeclarative_baseを使う方法です。よく利用されるのはdeclarative_baseを利用する方ですが、declarative_baseの処理を理解するためにはMetaDataを使う方法の処理の流れを知っている必要があるのでそれぞれ順番に説明します。

MetaDataを使う場合

“MetaData"はテーブルオブジェクト(テーブルと紐づいたクラス)をまとめて管理するためのものであり、管理対象となったテーブルに対して作成や削除、更新など様々な操作ができます。実際のコードを見てみましょう。

from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey

# MetaDataをインスタンス化
metadata = MetaData()

# Tableインスタンスを作成(第一引数:テーブル名,第二引数:metadata、第三引数以降:カラム)
# また、同時にmetadataの管理対象としてusersテーブルを登録
users_table = Table('users',
                    metadata,
                    Column('id', Integer, primary_key=True),
                    Column('name', String),
                    Column('fullname', String),
                    Column('password', String))

# engineで設定されているDBにmetadataの管理化にあるテーブルをすべて作成
metadata.create_all(engine)

“metadata.create_all"メソッドは同名のテーブルがすでに存在しているかの確認が行われるため、複数回実行しても同じテーブルが重複して作成されることはありません。

カラムなどテーブルの内容を変更したTableインスタンスを再度生成してからcreate_allしても、同じ名前のテーブルがすでに存在する場合、テーブルの情報は更新されないので注意しましょう。作り直す場合には一度テーブルを消してからcreate_allする必要があります。

DB操作用クラスの作成

上記で作成したTableインスタンス(users_table)はあくまでもDBにテーブルを作成するためのものであり、テーブル内のレコード情報を操作するインスタンスではありません。

テーブル作成用のクラスとは別に、レコード情報を受け取るためのクラスを作成する必要があります。

作り方は以下の通りです。

class User:
    def __init__(self, name, fullname, password):
        self.name = name
        self.fullname = fullname
        self.password = password

    def __repr__(self):
        # __repr__メソッドは"print(Userインスタンス)"した時に出力する値を決めることができる。
        return "<User('%s','%s', '%s')>" % (self.name, self.fullname, self.password)

カラム名をそのままインスタンス変数として設定したクラスを作成します。

コード内のコメントでも記載していますが、__repr__メソッドは"print(Userインスタンス)"を実行した時に出力する値を決めることが出来ます。以下に__repr__メソッドを利用した場合としていない場合の実行結果の違いをお見せします。

# __repr__がない場合
>>> class User:
...     def __init__(self, name, fullname, password):
...         self.name = name
...         self.fullname = fullname
...         self.password = password
...

>>> ed_user = User('ed', 'Ed Jones', 'edspassword')
>>> print(ed_user)
<__main__.User object at 0x7fb07929c320>
# この出力結果ではインスタンスがどんな値を持っているか分からない。

# __repr__がある場合
>>> class User:
...     def __init__(self, name, fullname, password):
...         self.name = name
...         self.fullname = fullname
...         self.password = password
...     def __repr__(self):
...         return "<User('%s','%s', '%s')>" % (self.name, self.fullname, self.password)
...

>>> ed_user = User('ed', 'Ed Jones', 'edspassword')
>>> print(ed_user)
<User('ed','Ed Jones', 'edspassword')>
# インスタンス変数の中身が見れる!

DB操作用クラスとDBの紐付け

テーブルとテーブルをPythonで扱うためのクラスが出来ました。
次にこの2つを紐付ける必要があります。この紐付けのことをマッピングと呼びます

コード例は以下です。

>>> from sqlalchemy.orm import mapper
>>> mapper(User, users_table)
<Mapper at 0x7fb07531ce10; User>

mapper関数を使ってDB操作用のUserクラスとusersテーブル自体を表すusers_tableインスタンスとを紐づけています。"Mapper at ~"の~はメモリ上のどこに紐付け情報が保存されているかを示しています。

これでSQLAlchemyのORM機能を使う準備は出来ました。それでは実際に動かしてみましょう。

SQLAlchemyを利用したテーブル情報操作

usersテーブルと紐付けられたクラスからインスタンスを作成して、色々な操作を試してみます。

# マッピング完了後に以下を実行

>>> ed_user = User('ed', 'Ed Jones', 'edspassword')

>>> ed_user.name
'ed'

>>> ed_user.password
'edspassword'

>>> print(ed_user.id)
None

ed_user.idを実行してもNoneが返ってきており、エラーが発生していないことに注目してください。

もともとUserというクラスにはidという変数は設定していません。
通常インスタンスに存在しない変数を参照しようとしたら以下のようにエラーが発生します。

>>> class Dog:
...     def __init__(self, name):
...         self.name = name
...

>>> pochi = Dog("pochi")
>>> pochi.name
'pochi'

>>> pochi.id
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Dog' object has no attribute 'id'

しかし、Userクラスはすでにusersテーブルにマッピングされています。

テーブルにマッピングされたクラスは、そのテーブルのカラムと同じ属性(変数)を持つようになります。Userクラスから生成されたインスタンスにはidという属性が追加され、参照してもエラーは発生しなくなります。

idの値はまだNoneですが、後述するcommitという作業を行うと値が入ることになります。

declarative_baseを使ってみる

テーブル定義とテーブル生成をするにはdeclarative_baseを使う方法もあります。

declarative_baseを使うと「metadata作成」「Tableオブジェクト作成」「クラスとのマッピング」を同時に行うことが出来ます。

以下がdeclarative_baseを使った場合のコード例になります。

# declarative_base関数のインポート
from sqlalchemy.ext.declarative import declarative_base

# MetaDataの機能や関連付けの機能を持つBaseオブジェクトを作成
Base = declarative_base()

# Baseを継承させることでBaseのもつMetaDataにこのクラスが登録される
class User(Base):
    # 関連づけるテーブル名を指定
    __tablename__ = 'users'

    # テーブルのカラム情報をクラス変数として持たせる
    id = Column(Integer, primary_key=True)
    name = Column(String)
    fullname = Column(String)
    password = Column(String)

    # id以外のカラム名のインスタンス変数を作成(idはDBに登録される際に自動で割り振られるので不要)
    def __init__(self, name, fullname, password):
        self.name = name
        self.fullname = fullname
        self.password = password

    # Userクラスのインスタンスの出力結果を設定(作成は任意)
    def __repr__(self):
        return "<User('%s','%s', '%s')>" % (self.name, self.fullname, self.password)

declarative_base関数で作成されたBaseクラスはmetadataを属性(クラス変数)として持っているので、以下のようにしてmetadataに登録されているクラス(上のコード例でいうとUserクラス)のテーブルを作成することができます。

>>> Base.metadata.create_all(engine)
~~出力一部省略~~
CREATE TABLE users (
        id SERIAL NOT NULL,
        name VARCHAR,
        fullname VARCHAR,
        password VARCHAR,
        PRIMARY KEY (id)
)
~~出力一部省略~~
# テーブル名"users"はUserクラスのクラス変数__tablename__に格納された文字列を取ってきている

セッションを作る

セッションとはDBとPythonコードを結びつける紐のようなものです。セッションがあることで、DBに実際にデータを追加したり、編集したりなどのDB操作が可能になります。

セッションは以下のようにして作ります。


>>> from sqlalchemy.orm import sessionmaker
>>> Session = sessionmaker(bind=engine)
>>> session = Session()
# Sessionクラスのインスタンス(session)にはエンジン設定が含まれている。
# このsessionを利用してDB操作を行う。

これにて前準備は完了です。それではここからテーブル操作に入りましょう。

テーブル操作

レコードの追加

エンジン作成、マッピング、セッション作成が完了し、ついにテーブル操作を始められるようになりました。まずは簡単にできるレコード追加を試してみましょう。

# usersテーブルとマッピングされているUserクラスからインスタンス化
>>> ed_user = User('ed', 'Ed Jones', 'edspassword')

# セッションに追加
>>> session.add(ed_user)
# この時点ではまだセッションに乗せているだけであり、DBの方には反映されていません。
# Amazonでカートには入れたけど、まだ購入は完了していない感じ

# DBに反映
>>> session.commit()
# commitが行われて初めてDB側に反映が確定します。
# Amazonでいうと購入を確定させた感じ

これでレコード追加は完了です。インスタンス化→add→commitの流れ。

次に今作成したレコード情報を取得してみましょう。

レコードの取得

レコードの取得は以下のコマンドで可能です。

session.query(~).all()

~の箇所には"欲しいテーブル情報とマッピングしているクラス名.カラム名"もしくは"クラス名"のみを指定します。クラス名のみを指定した場合には指定したクラスのインスタンスが返ってきます。つまり指定したテーブル全カラムの情報をまとめて取得できます。SQL文における*(アスタリスク)と同じです。

allメソッドで取得した場合にはレコード全てがリストに格納されて返ってきます。
filterメソッドを利用すると取得するレコードを特定の条件で絞り込むことができます。これはSQL文におけるwhereと同じです。

firstというメソッドもあり、これは条件に合致したレコードの中で先頭のレコードだけ返します。

ここでの一番気を付けないといけない点は、session.queryはDBだけでなくsessionにも問い合わせを行なっている点です。「DBに登録されている情報」+「セッションにaddしたがcommitはしていないデータ」がquery対象となります。コードで実際に確認してみましょう。

# 一人目はcommitまでする
>>> ed_user = User('ed', 'Ed Jones', 'edspassword')
>>> session.add(ed_user)
>>> session.commit()

# 二人目はcommitせずaddだけ
>>> ed_user2 = User('ed2', 'Ed2 Jones', 'ed2spassword')
>>> session.add(ed_user2)

# allの戻り値の確認
>>> session.query(User).all()
[<User('ed','Ed Jones', 'edspassword')>, <User('ed2','Ed2 Jones', 'ed2spassword')>]
# commitしていないed2のインスタンスまで持ってこれた

# ついでにfilterを使った場合
session.query(User).filter(User.name=='ed').all()
[<User('ed','Ed Jones', 'edspassword')>]
# filter条件に合致したedを取得し、合致しなかったed2は取得しなかった。

レコードの更新

レコードの更新は以下の手順で更新します。

  1. 更新したいレコードの情報をインスタンスの形で取得
  2. 取得したインスタンスの変数に代入
  3. commit

ちなみに1で取得したものはsessionに問い合わせて取得したものであり、すでにsessionに紐づいているためaddする必要がないことに注意しましょう。

# all()ではなくfirst()で取得するのでリストではなくインスタンスの形で返ってくる
>>> ed_user = session.query(User).filter(User.name=='ed').first()
>>> ed_user
<User('ed','Ed Jones', 'edspassword')>

# インスタンス変数に代入
>>> ed_user.name = 'edy'
>>> ed_user
<User('edy','Ed Jones', 'edspassword')

>>>> session.commit()
2019-08-21 22:52:04,419 INFO sqlalchemy.engine.base.Engine UPDATE users SET name=? WHERE users.id = ?
2019-08-21 22:52:04,419 INFO sqlalchemy.engine.base.Engine ('edy', 1)
2019-08-21 22:52:04,419 INFO sqlalchemy.engine.base.Engine COMMIT
# ログ出力されているSQL文からレコードの更新(UPDATEが行われていることが分かる)

レコードの削除

削除は簡単です。以下のように行えばOK

>>> session.query(User).filter(User.name=='edy').delete()
>>> session.commit()

# 消えたか確認
>>> ed_user = session.query(User).filter(User.name=='edy').first()
>>> ed_user
>>>
# ed_userを出力しようとしてみたが、何も出力されない
# これはqueryの結果が空であったことを表しているのでちゃんと削除されていることが分かる

まとめ

SQLAlchemyの使い方の説明は以上です。

テーブル同士にリレーションを張ったり、queryを投げる際にテーブルを結合させて取得なんてこともSQLAlchemyではできますが、その説明は別の記事でしようと思います。

最後に"全部読めて意味が理解できたらSQLAlchemyの基本はバッチリ"というコードを準備したので、知識の定着度合いを確認してみてください。

変数にはどんな値が入っているのか、DBはどんな状態なのか考えながら読み進めてみましょう。
最後に出力されるall_usersの値が分かるでしょうか?この記事の一番下に答えを載せてるので、合っているか確認してみましょう。

from sqlalchemy import create_engine
from sqlalchemy import Table, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker


engine = create_engine('sqlite:///:memory:', echo=True)
Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    fullname = Column(String)
    password = Column(String)

    def __init__(self, name, fullname, password):
        self.name = name
        self.fullname = fullname
        self.password = password

    def __repr__(self):
        return "<User('%s','%s', '%s')>" % (self.name, self.fullname, self.password)


Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()

ed_user = User('ed', 'Ed Jones', 'edspassword')
session.add(ed_user)
session.commit()

ed_user2 = User('ed2', 'Ed2 Jones', 'ed2spassword')
session.add(ed_user2)

ed_user = session.query(User).filter(User.name=='ed').first()
ed_user.name = 'edy'
session.query(User).filter(User.name=='ed').delete()
session.commit()

all_users = session.query(User).all()

print(all_users)

参考サイト

最後のall_usersの値

[<User('edy’,’Ed Jones’, 'edspassword’)>, <User('ed2′,’Ed2 Jones’, 'ed2spassword’)>]

この記事を読んだ方におすすめの記事

SQLAlchemyでDBからデータを取得するためのメソッドはたくさんあります。その中でもよく使われるのはfirstです。これと似たメソッドにoneとscalarがあるのですが、これらを使い分けられるとエンジニアとして1つレベルアップできます。

詳細は以下の記事をご確認ください。

PythonSQL,SQLAlchemy

Posted by ラプラス