Django ORM の基本(filter・exclude・get の使い分け)

Django ORM を使えば、SQL を書かずに Python のメソッドチェーンでデータベースを操作できます。中でも filterexcludeget はデータ取得の基本となる 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]
__isnullNULL判定body__isnull=True

__icontains__iexact のように i を付けると、大文字小文字を区別しない比較になります。

exclude で条件に合わないレコードを取得する

excludefilter の逆で、条件に一致しないレコードを返します。

# 下書きを除外(=公開済みの記事を取得)
articles = Article.objects.exclude(is_draft=True)

filterexclude は組み合わせることも可能です。

# 公開済みかつカテゴリが「雑記」でない記事
articles = Article.objects.filter(
    is_draft=False
).exclude(
    category='雑記'
)
filter

条件に一致するレコードを返す。結果は 0 件以上の QuerySet

exclude

条件に一致しないレコードを返す。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 件以上のときに発生します。ユニークなフィールド(idslug など)で検索する場合にのみ 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('公開済みの記事があります')

filterexcludeget の 3 つを軸にしつつ、firstcountexists などの補助メソッドを組み合わせることで、ほとんどのデータ取得パターンに対応できます。