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

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 やサブクエリも、
annotateaggregateSubquery などを使えば
かなりのところまでいけます。

初心者のうちは、

まず 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をコピーしました