Python | Web フレームワーク:Django ORM

Python Python
スポンサーリンク
スポンサーリンク

概要(Django ORM=「SQL を書かずに DB を触るための翻訳レイヤー」)

Django ORM は、

「データベース(SQL の世界)」と
「Python オブジェクト(クラスとインスタンスの世界)」

の間をつないでくれる翻訳レイヤーです。

本来なら、

SELECT * FROM book WHERE price < 1000;

のような SQL を書いてデータを取ってくるところを、
Django では

Book.objects.filter(price__lt=1000)
Python

のように「Python だけ」で書けるようにしてくれます。

ここから、

モデルと ORM の関係
QuerySet の考え方
基本的な CRUD 操作
フィルタ・並び替え・集計
リレーション(外部キー)をまたいだクエリ

を、初心者向けにかみ砕いて説明していきます。


モデルと ORM の関係をもう一度整理する

「モデル=テーブルの設計図」「ORM=そのテーブルを操作する道具」

まず前提として、Django では

モデル(models.Model を継承したクラス)
データベースのテーブル

が 1 対 1 で対応しています。

例えば、Book モデルがあるとします。

from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=200)
    price = models.IntegerField()

    def __str__(self):
        return self.title
Python

マイグレーションをすると、
DB には app名_book というテーブルができます。

ここで Django ORM がやってくれるのは、

この Book テーブルに対して
SELECT / INSERT / UPDATE / DELETE を
Python のメソッド呼び出しとして書けるようにすること

です。

SQL を直接書く代わりに、
Book.objects という「マネージャ」を通して
クエリを組み立てていきます。

QuerySet=「まだ実行されていないクエリの“設計図”」

Book.objects.all() のような呼び出しは、
「QuerySet(クエリセット)」というオブジェクトを返します。

qs = Book.objects.all()
Python

この時点では、
まだ DB に対して SQL が実行されていないことが多いです。

QuerySet は「どんな条件でデータを取りたいか」という
クエリの設計図のようなもので、

実際にデータが必要になったタイミング(for で回す、list() にする、len() を取るなど)で
初めて SQL が発行されます。

この「遅延評価」の仕組みのおかげで、

フィルタを重ねて書いても無駄な SQL が増えない
同じ QuerySet を再利用しやすい

といったメリットがあります。

初心者のうちは、

Book.objects.all() は「Book 全部の“集合”」
filter() や exclude() は「その集合を絞り込んだ新しい集合を返す」

くらいの感覚で捉えておけば十分です。


基本の CRUD 操作を Django ORM で体に入れる

Create(作成):オブジェクトを作って save する

新しい Book を 1 件追加したいとき。

from app.models import Book

book = Book(title="Python入門", price=1500)
book.save()
Python

これで、DB に 1 行 INSERT されます。

save() を呼ぶ前の book は、
まだ DB に保存されていない「ただの Python オブジェクト」です。
save() を呼んだ瞬間に SQL が発行されます。

ショートカットとして create() もよく使います。

book = Book.objects.create(title="Django実践", price=2000)
Python

これは「インスタンスを作って、すぐ save までやる」一発書きです。

Read(読み取り):all, get, filter

全件取得は all() です。

books = Book.objects.all()
Python

これは「Book 全部の QuerySet」です。
for で回せます。

for book in books:
    print(book.title, book.price)
Python

主キー(id)で 1 件だけ取りたいときは get()。

book = Book.objects.get(id=1)
Python

存在しない id を指定すると DoesNotExist 例外が出ます。
複数ヒットしても例外です(「1 件だけ」の前提)。

条件で絞り込みたいときは filter()。

cheap_books = Book.objects.filter(price__lt=1000)
Python

price__lt=1000 は「price < 1000」という意味です。
__lt は less than(未満)、
他にも __lte(以下)、__gt(より大きい)、__gte(以上)などがあります。

filter() は「条件に合うものだけを残した新しい QuerySet」を返します。

Update(更新):取り出して値を変えて save

1 件だけ更新したいときは、

book = Book.objects.get(id=1)
book.price = 1800
book.save()
Python

これで id=1 の行の price が UPDATE されます。

複数行をまとめて更新したいときは update()。

Book.objects.filter(price__lt=1000).update(price=1000)
Python

これは「price が 1000 未満の本を全部、price=1000 にする」
という SQL を一発で発行します。

Delete(削除):delete を呼ぶ

1 件削除。

book = Book.objects.get(id=1)
book.delete()
Python

条件に合うものをまとめて削除。

Book.objects.filter(price__lt=500).delete()
Python

ここまでが CRUD の基本です。

重要なのは、

「SQL を意識せずに、Python のメソッド呼び出しとして書ける」
「QuerySet を組み立ててから、必要なタイミングで実行される」

という感覚を掴むことです。


filter の書き方をもう少し深掘りする(lookup の世界)

フィールド名__lookup というパターンを覚える

price__lt=1000 のように、
フィールド名のあとに __ で lookup をつなげる書き方が
Django ORM の基本パターンです。

よく使うものをいくつか挙げます。

price__lt=1000 未満
price__lte=1000 以下
price__gt=1000 より大きい
price__gte=1000 以上

文字列に対しては、

title__contains="Python" 部分一致(大文字小文字区別あり)
title__icontains="python" 部分一致(大文字小文字区別なし)
title__startswith="入門" 前方一致
title__endswith="完全ガイド" 後方一致

日付や日時に対しては、

created_at__date=date(2025, 1, 1)
created_at__year=2025
created_at__month=1

なども使えます。

この「フィールド名__lookup」という書き方に慣れると、
かなり直感的にクエリを書けるようになります。

複数条件の AND / OR

AND 条件は、filter() をつなげるか、
キーワード引数を並べるだけです。

Book.objects.filter(price__gte=1000, price__lte=2000)
Book.objects.filter(price__gte=1000).filter(price__lte=2000)
Python

どちらも「1000〜2000 の本」を意味します。

OR 条件は少しだけ特殊で、Q オブジェクトを使います。

from django.db.models import Q

Book.objects.filter(Q(price__lt=500) | Q(title__icontains="入門"))
Python

これは「価格が 500 未満、またはタイトルに ‘入門’ を含む本」です。

初心者のうちは、
まず AND 条件(filter の組み合わせ)に慣れてから、
必要になったときに OR(Q オブジェクト)を覚えれば十分です。


並び替え・件数・存在チェックなどの便利メソッド

order_by で並び替え

価格の安い順に並べたいとき。

Book.objects.all().order_by("price")
Python

高い順にしたいときは、先頭に - を付けます。

Book.objects.all().order_by("-price")
Python

複数キーで並び替えもできます。

Book.objects.all().order_by("price", "title")
Python

count, exists で軽量なチェック

件数だけ知りたいときは count()。

Book.objects.filter(price__lt=1000).count()
Python

これは SQL の SELECT COUNT(*) に対応します。

「1 件でも存在するかどうか」だけ知りたいときは exists()。

Book.objects.filter(price__lt=1000).exists()
Python

True / False が返ってきます。
実際のレコードを全部取ってくるより軽いです。

first, last で先頭・末尾を 1 件だけ

Book.objects.order_by("price").first()
Book.objects.order_by("price").last()
Python

それぞれ「一番安い本」「一番高い本」を 1 件だけ返します。
存在しない場合は None です。


リレーション(外部キー)をまたいだクエリ

モデル同士の関係を ORM でどう辿るか

例えば、Author と Book の関係を考えます。

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=200)
    price = models.IntegerField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
Python

Book は Author への外部キーを持っています。

Python の世界では、

book.author.name
Python

のように、
Book から Author を辿れます。

逆に、Author からその人の本一覧を取りたいときは、

author.book_set.all()
Python

book_set は「逆参照」のデフォルト名です
(related_name で変えられます)。

リレーションを条件にした filter(author__name など)

「著者名が ‘山田太郎’ の本だけ欲しい」とします。

Book.objects.filter(author__name="山田太郎")
Python

ここでも __ が出てきます。

author__name は、

Book の author フィールド(Author への外部キー)を辿り
その Author の name フィールドを見て条件をかける

という意味です。

このように、

外部キー名__相手のフィールド名__lookup

という形で、
リレーションをまたいだクエリを自然に書けます。

逆方向(Author から Book を条件にする)も同様です。

Author.objects.filter(book__price__lt=1000)
Python

これは「自分の書いた本の中に、価格 1000 未満のものがある著者」です。

リレーションをまたいだクエリは、
最初は少し不思議に感じるかもしれませんが、

「__ でつなぐたびに、1 テーブル分 JOIN している」

くらいのイメージを持つと理解しやすくなります。


設計のポイント(ORM に“何を任せて、何を自分で意識するか”)

「まず ORM で書いてみて、どうしても無理なら生 SQL」を基本にする

Django ORM はかなり強力なので、
普通の業務アプリで必要なクエリの 9 割以上は
ORM だけで書けます。

複雑な JOIN やサブクエリも、
annotate や aggregate、Subquery などを使えば
かなりのところまでいけます。

初心者のうちは、

まず ORM で素直に書いてみる
どうしても表現しづらいときだけ raw SQL を検討する

というスタンスで十分です。

いきなり SQL に逃げると、
Django の恩恵(DB 抽象化、移植性、テストのしやすさ)を
自分から捨てることになります。

「QuerySet は“集合”として扱う」感覚を持つ

Book.objects.all() や filter() の結果は、
「Book の集合」です。

この集合に対して、

さらに filter する
order_by する
count する

といった操作を重ねていくイメージを持つと、
ORM のコードが自然な形になります。

例えば、

「1000 円以上の本の中で、’Python’ を含むタイトルのものを
価格の高い順に並べて、上位 5 件だけ欲しい」

という要件は、
こう書けます。

Book.objects.filter(price__gte=1000, title__icontains="python") \
           .order_by("-price")[:5]
Python

SQL を直接書くより、
かなり読みやすいはずです。


まとめ(Django ORM は「DB のめんどくささを隠してくれる相棒」)

Django ORM を初心者目線で整理すると、こうなります。

モデル(models.Model)はテーブルの設計図で、ORM はそのテーブルに対する SELECT / INSERT / UPDATE / DELETE を Python のメソッドとして書けるようにする翻訳レイヤー。
Book.objects.all() や filter() が返す QuerySet は「まだ実行されていないクエリの設計図」で、必要になったタイミングで SQL が発行される。
CRUD は「create → save」「get / filter」「値を変えて save」「delete」というパターンを押さえればよく、lookup(price__lt など)に慣れるとかなり表現力が上がる。
リレーション(外部キー)をまたいだクエリも、author__name のように __ でつなぐだけで書けるので、「JOIN を意識せずに JOIN した結果」を扱える。

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