7.

Django ORM filter 完全ガイド(lookup 一覧・Q オブジェクト・F 式・annotate)

編集
この記事の要点
  • Django ORMModel.objects.filter()lookup(__icontains, __gte 等)で柔軟に検索条件を組み立てる
  • 主要 lookup: exact / iexact / contains / icontains / startswith / endswith / gt / gte / lt / lte / in / range / isnull / regex / date / year / month
  • OR 条件は Q オブジェクト: Q(a=1) | Q(b=2)。NOT は ~Q(...)
  • 別カラム同士の比較や演算は F 式: filter(price__gt=F("cost"))
  • 集約後にフィルタ: annotate + filter。例: .annotate(c=Count("items")).filter(c__gte=3)

filter の基本

filter()SQL WHERE 句を Python から組み立てるメソッドです。返り値は QuerySet(遅延評価)で、実行は list() / for / .exists() 等で行われます。

from myapp.models import User, Article

# 単一条件
users = User.objects.filter(name="太郎")
# SQL: SELECT * FROM users WHERE name = '太郎'

# 複数条件(AND)
users = User.objects.filter(name="太郎", age=30)

# チェーン(同じく AND)
users = User.objects.filter(active=True).filter(role="admin")

# 否定
inactive = User.objects.exclude(active=True)

# 件数
count = User.objects.filter(active=True).count()

# 存在確認(速い)
if User.objects.filter(email="x@example.com").exists():
    pass

# 1件取得
u = User.objects.filter(id=1).first()       # None になり得る
u = User.objects.get(id=1)                  # 0件 / 2件以上で例外

lookup 一覧

lookup意味SQL 例
exact完全一致(デフォルト)= 'a'
iexact大小無視の完全一致LOWER(...) = 'a'
contains部分一致LIKE '%a%'
icontains大小無視の部分一致ILIKE '%a%'
startswith / istartswith前方一致LIKE 'a%'
endswith / iendswith後方一致LIKE '%a'
gt / gte / lt / lte大小比較> / >= / < / <=
inリスト内IN (1, 2, 3)
range範囲(両端含む)BETWEEN a AND b
isnullNULL 判定IS NULL / IS NOT NULL
regex / iregex正規表現REGEXP
date / year / month / day日付の部分抽出YEAR(d) = 2026
hour / minute / week_day時刻・曜日同上

lookup の使用例

# 大小無視で部分一致
Article.objects.filter(title__icontains="django")

# 18 歳以上
User.objects.filter(age__gte=18)

# 18-65 歳
User.objects.filter(age__range=(18, 65))

# 特定の ID 群
User.objects.filter(id__in=[1, 2, 3])

# email が NULL
User.objects.filter(email__isnull=True)
# email が NOT NULL
User.objects.filter(email__isnull=False)

# 2026 年の記事
Article.objects.filter(published_at__year=2026)
Article.objects.filter(published_at__year=2026, published_at__month=5)

# 日付範囲
from datetime import date
Article.objects.filter(published_at__date__range=(date(2026, 1, 1), date(2026, 5, 17)))

# 正規表現
User.objects.filter(email__regex=r"^[a-z]+@example\.com$")

# 外部キーを辿る(__ で何段でも)
Article.objects.filter(author__profile__country="JP")

Q オブジェクト: OR / NOT / 複雑条件

filter のキーワード引数はすべて AND。OR や複雑な括弧条件は Q を使います:

from django.db.models import Q

# OR
User.objects.filter(Q(name="太郎") | Q(name="花子"))

# AND(同じだが明示的に)
User.objects.filter(Q(active=True) & Q(role="admin"))

# NOT
User.objects.filter(~Q(role="guest"))

# 複雑な括弧
# (active=True AND role='admin') OR (vip=True)
User.objects.filter(
    (Q(active=True) & Q(role="admin")) | Q(vip=True)
)

# キーワード引数と Q の混在も OK(Q は前、キーワードは後ろ)
User.objects.filter(Q(role="admin") | Q(role="manager"), active=True)

F 式: 別カラム同士の比較

「price が cost より高い商品」のように同じテーブルの別カラム同士を比較するときは F:

from django.db.models import F

# 別カラム比較
Product.objects.filter(price__gt=F("cost"))

# 演算
Product.objects.filter(price__gt=F("cost") * 1.2)   # 価格が原価の 1.2 倍超

# 更新(読みと書きが 1 クエリで完結)
Product.objects.filter(stock__gt=0).update(stock=F("stock") - 1)

annotate + filter(集約後のフィルタ)

from django.db.models import Count, Avg, Sum

# 投稿数が 3 件以上のユーザ
User.objects.annotate(
    post_count=Count("articles")
).filter(post_count__gte=3)

# カテゴリ別の平均価格 100 円以上
Product.objects.values("category").annotate(
    avg_price=Avg("price")
).filter(avg_price__gte=100)

# annotate の前と後で filter は意味が違う
# 前: 集約対象を絞る   後: 集約結果を絞る
User.objects.filter(articles__published=True).annotate(c=Count("articles"))
# → 公開記事数

User.objects.annotate(c=Count("articles")).filter(c__gte=3)
# → 全記事数が 3 件以上のユーザ

django-filter(DRF と組合せ)

Django REST Framework でクエリパラメータからの絞り込みを簡単にするライブラリ:

# pip install django-filter

# filters.py
import django_filters
from .models import Article

class ArticleFilter(django_filters.FilterSet):
    title = django_filters.CharFilter(lookup_expr="icontains")
    min_views = django_filters.NumberFilter(field_name="views", lookup_expr="gte")
    published_after = django_filters.DateFilter(field_name="published_at", lookup_expr="gte")

    class Meta:
        model = Article
        fields = ["category", "author"]

# views.py (DRF)
from rest_framework import generics
from django_filters.rest_framework import DjangoFilterBackend

class ArticleListView(generics.ListAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_class = ArticleFilter

# GET /articles/?title=django&min_views=100&published_after=2026-01-01

パフォーマンスの注意

  • icontainsインデックスが効かない。大量データなら全文検索 (PostgreSQL trigram / Elasticsearch) を検討
  • 外部キーをまたぐ filter はJOIN が増えるselect_related / prefetch_related 併用
  • .count() より .exists() が速い(存在チェック用途なら)
  • filter(...).filter(...)多対多経由だと意味が変わる(同じレコードか別レコードか)
  • SQL を確認: print(queryset.query) または django-debug-toolbar

FAQ

Q: filterget の違い
A: filter は QuerySet(0 件でも OK)、get は単一オブジェクト(0 件 / 2 件以上で例外)。「無ければ None」なら .first()

Q: NULL を含めて検索したい
A: filter(Q(email="") | Q(email__isnull=True)) のように Q で OR。

Q: 動的に lookup を組み立てたい
A: **{f"{field}__icontains": keyword} のように辞書アンパックで渡す。または Q を reduce(operator.or_, [Q(...), ...])

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. MySQL/MariaDBへの接続
  2. sqliteへの接続
  3. SELECT, INSERT, UPDATE, DELETE
  4. 素のSQLを直接実行する方法
  5. Order by DESCの指定方法
  6. limit, offsetの指定方法
  7. filterの検索オプション
  8. django-filterのlookup_expr検索オプション
  9. モデルの内部結合(1対1)