Django ORM を使えば、SQL を書かずに Python のメソッドチェーンでデータベースを操作できます。中でも filter・exclude・get はデータ取得の基本となる 3 つのメソッドです。
QuerySet とは
Django ORM のクエリは QuerySet というオブジェクトを通じて組み立てます。QuerySet はデータベースへの問い合わせを「遅延評価」で扱うため、メソッドをチェーンしている段階では SQL は発行されません。実際にデータが必要になったタイミング(ループ・スライス・list() への変換など)で初めてクエリが実行されます。
# この時点では SQL は発行されない
qs = Article.objects.filter(is_draft=False)
# ここで初めて SQL が発行される
for article in qs:
print(article.title)
この遅延評価の仕組みにより、条件を段階的に組み立てても余分なクエリが走ることはありません。
filter で条件に合うレコードを取得する
filter は条件に一致するレコードを QuerySet として返します。結果が 0 件でも空の QuerySet が返るだけで、エラーにはなりません。
# 公開済みの記事を取得
articles = Article.objects.filter(is_draft=False)
# タイトルに「Python」を含む記事を取得
articles = Article.objects.filter(title__contains='Python')
filter はチェーンすることで AND 条件を表現できます。
# 公開済みかつタイトルに「Python」を含む記事
articles = Article.objects.filter(
is_draft=False
).filter(
title__contains='Python'
)
Django のフィールドルックアップはアンダースコア 2 つで接続します。よく使うルックアップを確認しておきましょう。
| ルックアップ | 意味 | 例 |
|---|---|---|
| __exact | 完全一致 | title__exact='Hello' |
| __contains | 部分一致 | title__contains='Django' |
| __gte | 以上 | id__gte=10 |
| ルックアップ | 意味 | 例 |
|---|---|---|
| __lte | 以下 | id__lte=100 |
| __in | リスト内 | id__in=[1, 2, 3] |
| __isnull | NULL判定 | body__isnull=True |
__icontains や __iexact のように i を付けると、大文字小文字を区別しない比較になります。
exclude で条件に合わないレコードを取得する
exclude は filter の逆で、条件に一致しないレコードを返します。
# 下書きを除外(=公開済みの記事を取得)
articles = Article.objects.exclude(is_draft=True)
filter と exclude は組み合わせることも可能です。
# 公開済みかつカテゴリが「雑記」でない記事
articles = Article.objects.filter(
is_draft=False
).exclude(
category='雑記'
)
条件に一致するレコードを返す。結果は 0 件以上の QuerySet
条件に一致しないレコードを返す。filter の逆の動きをする
get で 1 件だけ取得する
get は条件に一致するレコードが 1 件だけのときに使います。QuerySet ではなく、モデルインスタンスを直接返すのが特徴です。
# id が 1 の記事を取得
article = Article.objects.get(id=1)
print(article.title)
ただし、get は 0 件または 2 件以上ヒットすると例外を送出します。
from django.core.exceptions import ObjectDoesNotExist
try:
article = Article.objects.get(id=9999)
except Article.DoesNotExist:
print('記事が見つかりません')
Article.DoesNotExist は 0 件のとき、Article.MultipleObjectsReturned は 2 件以上のときに発生します。ユニークなフィールド(id や slug など)で検索する場合にのみ get を使うのが安全です。
all と first・last
all() はテーブルの全レコードを QuerySet として返します。
all_articles = Article.objects.all()
first() と last() は QuerySet の先頭・末尾のレコードをモデルインスタンスとして返します。結果が 0 件の場合は None を返すため、get のように例外が発生しません。
latest = Article.objects.order_by('-published_at').first()
「1 件だけ取得したいが例外処理を書きたくない」という場面では、filter(...).first() の方が get より扱いやすいこともあります。状況に応じて使い分けてください。
count と exists
レコードの件数を調べるには count() を使います。Python の len() と違い、count() は SQL の COUNT を発行するため、全レコードをメモリに読み込みません。
draft_count = Article.objects.filter(is_draft=True).count()
レコードが 1 件でも存在するかどうかだけを知りたいなら exists() が最も効率的です。
if Article.objects.filter(is_draft=False).exists():
print('公開済みの記事があります')
filter・exclude・get の 3 つを軸にしつつ、first・count・exists などの補助メソッドを組み合わせることで、ほとんどのデータ取得パターンに対応できます。