概要(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)
Pythonprice__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=2025created_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")
Pythoncount, exists で軽量なチェック
件数だけ知りたいときは count()。
Book.objects.filter(price__lt=1000).count()
Pythonこれは SQL の SELECT COUNT(*) に対応します。
「1 件でも存在するかどうか」だけ知りたいときは exists()。
Book.objects.filter(price__lt=1000).exists()
PythonTrue / 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)
PythonBook は Author への外部キーを持っています。
Python の世界では、
book.author.name
Pythonのように、
Book から Author を辿れます。
逆に、Author からその人の本一覧を取りたいときは、
author.book_set.all()
Pythonbook_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]
PythonSQL を直接書くより、
かなり読みやすいはずです。
まとめ(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 した結果」を扱える。
