Python | DB・SQL:ORM モデル

Python
スポンサーリンク

ORM モデルって何?まずはイメージから

「ORM モデル」は、
データベースのテーブルを「Python のクラスとして表現したもの」です。

ORM は Object Relational Mapping(オブジェクトとリレーショナルの対応付け)の略で、
「テーブルの 1 行を Python のオブジェクトとして扱えるようにする仕組み」です。

テーブル=クラス
行=インスタンス

この対応が、ORM モデルの一番大事なイメージです。
これが腹落ちすると、SQL を直接書かなくても、
Python のコードだけで INSERT / SELECT / UPDATE / DELETE が書ける意味が見えてきます。


テーブルと ORM モデルの対応を具体例で見る

素の SQL でのテーブル定義

まず、SQL だけで users テーブルを作るとします。

CREATE TABLE users (
    id    INTEGER PRIMARY KEY AUTOINCREMENT,
    name  TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL
);
SQL

このテーブルは、「ユーザーの一覧」を表しています。
1 行が 1 人のユーザーです。

ここまでは、普通の SQL の世界です。

同じものを ORM モデルで書く(SQLAlchemy)

これを SQLAlchemy の ORM モデルで書くと、こうなります。

from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String, nullable=False)
    email = Column(String, unique=True, nullable=False)
Python

ここで起きていることを整理すると、
User クラスが users テーブルを表している。
User クラスの id, name, email が、テーブルのカラムを表している。
primary_key, unique, nullable などの制約も、クラス側に書いている。

つまり、「さっきの CREATE TABLE の情報を、Python のクラスとして書き直した」のが ORM モデルです。


ORM モデルの一番大事な対応関係

「クラス=テーブル」「インスタンス=行」をちゃんと掴む

User クラスを定義したら、次のようなコードが書けます。

user = User(name="Taro", email="taro@example.com")
Python

この user は、「users テーブルの 1 行になる予定のデータ」です。
まだ DB には保存されていませんが、「1 行分の情報を持ったオブジェクト」です。

セッションを使って保存すると、これが実際の行になります。

from sqlalchemy.orm import sessionmaker

engine = create_engine("sqlite:///example.db")
SessionLocal = sessionmaker(bind=engine)

Base.metadata.create_all(engine)  # テーブル作成

session = SessionLocal()
user = User(name="Taro", email="taro@example.com")
session.add(user)
session.commit()
session.close()
Python

ここで、

User クラス=users テーブル
user インスタンス=users テーブルの 1 行

という対応が、実際に「DB に書き込まれる形」で動いています。

この感覚がつかめると、
「ORM モデルを触る=テーブルの行を触る」
という意識でコードを書けるようになります。


ORM モデルを使った CRUD の流れ

作成(Create)=インスタンスを作って保存する

新しいユーザーを作るときは、
Python 的には「インスタンスを作る → セッションに add → commit」です。

session = SessionLocal()

new_user = User(name="Hanako", email="hanako@example.com")
session.add(new_user)
session.commit()

session.close()
Python

SQL にすると、

INSERT INTO users (name, email)
VALUES ('Hanako', 'hanako@example.com');
SQL

ですが、ORM モデルを使うと、
「INSERT 文を書く」のではなく「オブジェクトを作る」という感覚になります。

読み取り(Read)=クエリでモデルを取得する

ユーザーを検索するときは、
「User をクエリする」という書き方になります。

session = SessionLocal()

user = session.query(User).filter_by(email="hanako@example.com").first()
print(user.id, user.name, user.email)

session.close()
Python

戻ってくるのは dict ではなく User インスタンスです。
user.name のように属性アクセスで扱えるのが、ORM モデルの気持ちよさです。

更新(Update)=属性を書き換えて commit

更新は、「オブジェクトを取ってきて、属性を書き換えて、commit」です。

session = SessionLocal()

user = session.query(User).filter_by(email="hanako@example.com").first()
user.name = "Hanako Yamada"
session.commit()

session.close()
Python

SQL なら、

UPDATE users
SET name = 'Hanako Yamada'
WHERE email = 'hanako@example.com';
SQL

ですが、ORM モデルでは「Python のオブジェクトの属性を書き換える」という形で表現できます。

削除(Delete)=オブジェクトを delete して commit

削除も同じ流れです。

session = SessionLocal()

user = session.query(User).filter_by(email="hanako@example.com").first()
session.delete(user)
session.commit()

session.close()
Python

SQL なら、

DELETE FROM users WHERE email = 'hanako@example.com';
SQL

ですが、ORM モデルでは「オブジェクトを delete する」という操作になります。


リレーション(関係)を ORM モデルで表現する

1 対多の関係をクラスで書く

ORM モデルの真価は、「テーブル同士の関係もクラスで表現できる」ことです。
例えば、User と Post(記事)の関係を考えます。

User 1 人は複数の Post を持つ(1 対多)
Post 1 件は 1 人の User に属する

これを SQL で書くと、Post に user_id 外部キーを持たせます。

CREATE TABLE users (
    id   INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL
);

CREATE TABLE posts (
    id      INTEGER PRIMARY KEY AUTOINCREMENT,
    title   TEXT NOT NULL,
    body    TEXT NOT NULL,
    user_id INTEGER NOT NULL REFERENCES users(id)
);
SQL

同じものを ORM モデルで書くと、こうなります。

from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)

    posts = relationship("Post", back_populates="author")

class Post(Base):
    __tablename__ = "posts"

    id = Column(Integer, primary_key=True)
    title = Column(String, nullable=False)
    body = Column(String, nullable=False)
    user_id = Column(Integer, ForeignKey("users.id"))

    author = relationship("User", back_populates="posts")
Python

ここで重要なのは、
ForeignKey で「どのカラムがどのテーブルを参照しているか」を書き、
relationship で「Python 側のオブジェクト同士のつながり」を書いていることです。

リレーションを使った操作のイメージ

このモデルがあると、次のようなコードが書けます。

session = SessionLocal()

user = User(name="Taro")
post1 = Post(title="Hello", body="First post", author=user)
post2 = Post(title="Second", body="Another post", author=user)

session.add(user)
session.add_all([post1, post2])
session.commit()

session.close()
Python

Post を作るときに author=user と書いているのがポイントです。
これで post.user_id に user.id が入り、
DB 的にも「この Post はこの User に属する」という関係が作られます。

取り出すときは、こうです。

session = SessionLocal()

u = session.query(User).filter_by(name="Taro").first()
for p in u.posts:
    print(p.title)

session.close()
Python

ここで u.posts は、
裏で JOIN されたクエリの結果を、
「Post オブジェクトのリスト」として見せてくれています。

JOIN の SQL を自分で書かなくても、
「オブジェクト同士の関係」として扱えるのが、ORM モデルの大きなメリットです。


Django の ORM モデルとの比較で理解を深める

Django のモデルも「ORM モデル」

もし Django を触ったことがあれば、
Django の models.Model も、まさに ORM モデルです。

例えば、Django での User もどきを書くとこうなります。

from django.db import models

class User(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField(unique=True)
Python

これも、

User クラス=users テーブル
User インスタンス=1 行

という対応です。

使い方も同じで、

user = User.objects.create(name="Taro", email="taro@example.com")
user = User.objects.get(email="taro@example.com")
user.name = "Taro Yamada"
user.save()
user.delete()
Python

という流れになります。

SQLAlchemy でも Django でも、
「ORM モデル」という考え方は共通です。
クラスでテーブルを表し、インスタンスで行を表す。
これさえ掴めば、ORM の種類が変わっても応用が効きます。


まとめ(ORM モデルは「DB をオブジェクトとして扱うための土台」)

ORM モデルを初心者目線でまとめると、こうなります。

テーブルを Python のクラスとして表現したものが ORM モデルで、クラス=テーブル、インスタンス=行という対応になっている。
INSERT / SELECT / UPDATE / DELETE を、SQL ではなく「オブジェクトの生成・検索・属性変更・削除」として書けるようになる。
リレーション(1 対多、多対多など)もクラスに書けるので、JOIN を意識せずに「オブジェクト同士の関係」として扱える。
SQLAlchemy でも Django でも、根っこにある考え方は同じなので、一度「ORM モデルの感覚」が身につけば他のフレームワークにも応用できる。

ここから一歩進むなら、
「自分で簡単なアプリのモデルを設計してみる」のが一番身につきます。

例えば、
タスク管理なら「User」「Task」
読書ログなら「User」「Book」「Review」

タイトルとURLをコピーしました