1. はじめに
Django ORMのダブルアンダースコア (__) は、「検索条件の指定(Lookup)」と「テーブル結合(リレーション横断)」などの区切り文字に使われます。
基本パターンは次の3つに集約され、filter()だけでなくorder_by()やvalues()、annotate()などORM全体で共通して利用されます。
-
field__lookup(例:price__gte=500/ 検索条件を指定) -
relation__field(例:author__name="夏目漱石"/ 外部キー先を参照) -
relation__field__lookup(例:author__name__icontains="漱"/ 外部キー先を検索)
relationの表記について
author__nameなどのrelation部分は、ForeignKey、OneToOneField、ManyToManyField、または逆参照(related_name等)といったモデル間のリレーション(関連)を定義しているフィールド名を指します。
authorもモデル定義上はフィールドの一種ですが、単なる文字列や数値などの値ではなく「別モデルへの参照(テーブル結合/JOIN)」であることを明確にするため、本記事ではfield__fieldではなくrelation__fieldと区別して表記しています。
-
relation:参照元の関連フィールド名(例:ForeignKeyのauthor) -
field:リレーション先モデルのカラム名(例:name)
2. 前提
- uv: 0.12.7
- Python: 3.14
- Django: 6.1
3. 基本構文パターン
3.1 Lookup(検索条件)
フィールドに対する検索条件を指定します(クラス定義を含むサンプル全体は後述)。
Book.objects.filter(title__contains="猫")
Book.objects.filter(price__gte=700)
代表例
__exact__iexact__contains__icontains__startswith__istartswith__endswith__iendswith__gt__gte__lt__lte__in__range__isnull__regex__iregex__year__month__day__week__week_day__quarter__date__time
形式
field__lookup=value
否定したい場合
-
exclude()で一致するものを除外(例:Book.objects.exclude(title="Django")) -
~Q()(Qオブジェクトの否定)を使う(例:Book.objects.filter(~Q(title="Django")))
代表例以外のLookup
- https://docs.djangoproject.com/en/6.1/ref/models/querysets/#field-lookups
- https://docs.djangoproject.com/en/6.1/topics/db/queries/#querying-jsonfield
- https://docs.djangoproject.com/en/6.1/howto/custom-lookups/
3.2 Relation Traversal(リレーション横断)
ForeignKey / OneToOneField / ManyToManyFieldを辿ります。複数段も可能です。
Book.objects.filter(author__name="夏目漱石")
BookLog.objects.filter(book__author__name="夏目漱石")
形式
relation__field
relation__relation__field
3.3 Reverse Relation(逆参照)
ForeignKeyやManyToManyFieldの定義元モデルを逆方向に辿って検索します。
Author.objects.filter(book__title="吾輩は猫である")
デフォルト名
デフォルトでは、QuerySet内は<小文字モデル名>__<フィールド>で逆参照します。
Author.objects.filter(book__title="Django")
Group.objects.filter(user__email="asample@example.com")
Project.objects.filter(device__status="active")
related_nameを使う場合
related_nameを明示的に付与してbooks__titleのように参照すると、読みやすくなることがあります。
class Book2(models.Model):
author = models.ForeignKey(
Author,
on_delete=models.CASCADE,
related_name="books",
)
title = models.CharField(max_length=100)
Author.objects.filter(books__title="Django")
3.4 Relation Traversal + Lookup
リレーション先のフィールドに対して検索します。
Book.objects.filter(author__name__contains="漱")
形式
relation__field__lookup
3.5 JSONFieldのキー参照
JSONのキーを__で辿ることができます(キー名がLookup名と衝突する場合は使えません)。
BookLog.objects.filter(data__tag=["小説"])
ネストしたキーも参照可能です。
Book.objects.filter(booklog__data__tag=["小説"])
整数キーを使うと、配列要素を参照できます。
BookLog.objects.filter(data__tag__0="小説")
形式
jsonfield__key
jsonfield__key__nested_key
4. 各QuerySetメソッド・機能での利用
4.1 order_by()
関連先フィールドでソートします。
Book.objects.order_by("author__name")
Book.objects.order_by("-author__name")
形式
order_by("relation__field")
4.2 values() / values_list()
関連先フィールドを取得します。
Book.objects.values("author__name")
Book.objects.values_list("author__name")
形式
values("relation__field")
values_list("relation__field")
4.3 annotate() + F()オブジェクト
annotate()は、関連先フィールドを利用した注釈列を作成します。
F()オブジェクトはDB上でフィールド参照を行う書き方です。
from django.db.models import F
Book.objects.annotate(
author_name=F("author__name")
).values("author_name")
形式
F("relation__field")
4.4 aggregate() / annotate() + Avg
関連先フィールドや逆参照を利用して集約できます。
from django.db.models import Avg
Author.objects.aggregate(avg_price=Avg("book__price"))
Author.objects.annotate(
avg_price=Avg("book__price")
).values("avg_price")
補足
-
aggregate()の例は全著者の平均を辞書で返す -
annotate()の例は著者ごとの平均をQuerySetで返す - 実際の参照名は
related_nameの設定に依存する
5. 注意点・制限事項
5.1 Constraint(制約)の制限
ConstraintではフィールドやLookupを利用できます。
from django.db.models import Q
from django.db.models.constraints import CheckConstraint
CheckConstraint(
condition=Q(price__gte=0),
name="price_positive",
)
しかし、Djangoのデータベース制約はJOINをまたぐ条件を扱えないため、関連先フィールドをCheckConstraintで直接参照する設計はできません。
5.2 Index(インデックス)の制限
通常のIndexは同一テーブルのフィールドに対して作成します。
models.Index(
fields=["name"],
name="name_idx",
)
しかし、author__nameのようなJOIN先のフィールドに、models.Indexで対象モデルから直接インデックスを作ることはできません。
5.3 その他
Lookupを省略するとexactになります。次の2行は同じ意味です。
Book.objects.filter(title="Django")
Book.objects.filter(title__exact="Django")
リレーション横断ではselect_related()(主にForeignKey/OneToOne)やprefetch_related()(主にManyToMany)の利用も検討してください。
リレーションは複数段繋げて参照できますが、JOINの増加によるクエリ性能の低下や保守性の悪化を招くことがあります。QuerySet.explain()や実行時間を確認し、必要であればテーブル設計を見直すとよいでしょう。
6. まとめ
Django ORMにおいて特別な意味を持つ記法として、重要なのがダブルアンダースコア (__) です。
利用パターンは主に次の3種類です。
field__lookup
relation__field
relation__field__lookup
filter()、exclude()、get()、order_by()、values()、values_list()、annotate()、aggregate()など多くのORM APIで利用できます。
7. 参考:サンプルコード
以下をsample.pyとし、uv run -p 3.14 -w django==6.1 sample.pyのように実行してください。
import django
from django.conf import settings
from django.db import connection, models
from django.db.models import Avg, F
settings.configure(
DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}},
INSTALLED_APPS=["__main__"],
)
django.setup()
class Author(models.Model):
name = models.CharField(max_length=100)
def __str__(self) -> str:
return self.name
class Book(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
price = models.IntegerField()
def __str__(self) -> str:
return self.title
class BookLog(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE)
date = models.DateField()
data = models.JSONField()
def __str__(self) -> str:
return f"{self.date} {self.book}"
with connection.schema_editor() as schema_editor:
schema_editor.create_model(Author)
schema_editor.create_model(Book)
schema_editor.create_model(BookLog)
author1 = Author.objects.create(name="夏目漱石")
author2 = Author.objects.create(name="宮沢賢治")
book = Book.objects.create(author=author1, title="吾輩は猫である", price=600)
Book.objects.create(author=author1, title="坊っちゃん", price=510)
Book.objects.create(author=author2, title="銀河鉄道の夜", price=750)
BookLog.objects.create(book=book, date="2026-09-01", data={"tag": ["小説"]})
print("\n### 3.1 Lookup(検索条件)")
print(f"{Book.objects.filter(title__contains="猫") = }")
print(f"{Book.objects.filter(price__gte=700) = }")
print("\n### 3.2 Relation Traversal(リレーション横断)")
print(f"{Book.objects.filter(author__name="夏目漱石") = }")
print(f"{BookLog.objects.filter(book__author__name="夏目漱石") = }")
print("\n### 3.3 Reverse Relation(逆参照)")
print(f"{Author.objects.filter(book__title="吾輩は猫である") = }")
print("\n### 3.4 Relation Traversal + Lookup")
print(f"{Book.objects.filter(author__name__contains='漱') = }")
print("\n### 3.5 JSONFieldのキー参照")
print(f"{BookLog.objects.filter(data__tag=["小説"]) = }")
print(f"{Book.objects.filter(booklog__data__tag=["小説"]) = }")
print(f"{BookLog.objects.filter(data__tag__0="小説") = }")
print("\n### 4.1 order_by()")
print(f"{Book.objects.order_by("author__name") = }")
print(f"{Book.objects.order_by("-author__name") = }")
print("\n### 4.2 values() / values_list()")
print(f"{Book.objects.values("author__name") = }")
print(f"{Book.objects.values_list("author__name") = }")
print("\n### 4.3 annotate() + F()オブジェクト")
print(
f"{Book.objects.annotate(
author_name=F("author__name")
).values("author_name") = }"
)
print("\n### 4.4 aggregate() / annotate() + Avg")
print(f"{Author.objects.aggregate(avg_price=Avg("book__price")) = }")
print(
f"{Author.objects.annotate(
avg_price=Avg("book__price")
).values("avg_price") = }"
)
出力
### 3.1 Lookup(検索条件)
Book.objects.filter(title__contains="猫") = <QuerySet [<Book: 吾輩は猫である>]>
Book.objects.filter(price__gte=700) = <QuerySet [<Book: 銀河鉄道の夜>]>
### 3.2 Relation Traversal(リレーション横断)
Book.objects.filter(author__name="夏目漱石") = <QuerySet [<Book: 吾輩は猫である>, <Book: 坊っちゃん>]>
BookLog.objects.filter(book__author__name="夏目漱石") = <QuerySet [<BookLog: 2026-09-01 吾輩は猫である>]>
### 3.3 Reverse Relation(逆参照)
Author.objects.filter(book__title="吾輩は猫である") = <QuerySet [<Author: 夏目漱石>]>
### 3.4 Relation Traversal + Lookup
Book.objects.filter(author__name__contains='漱') = <QuerySet [<Book: 吾輩は猫である>, <Book: 坊っちゃん>]>
### 3.5 JSONFieldのキー参照
BookLog.objects.filter(data__tag=["小説"]) = <QuerySet [<BookLog: 2026-09-01 吾輩は猫である>]>
Book.objects.filter(booklog__data__tag=["小説"]) = <QuerySet [<Book: 吾輩は猫である>]>
BookLog.objects.filter(data__tag__0="小説") = <QuerySet [<BookLog: 2026-09-01 吾輩は猫である>]>
### 4.1 order_by()
Book.objects.order_by("author__name") = <QuerySet [<Book: 吾輩は猫である>, <Book: 坊っちゃん>, <Book: 銀河鉄道の夜>]>
Book.objects.order_by("-author__name") = <QuerySet [<Book: 銀河鉄道の夜>, <Book: 吾輩は猫である>, <Book: 坊っちゃん>]>
### 4.2 values() / values_list()
Book.objects.values("author__name") = <QuerySet [{'author__name': '夏目漱石'}, {'author__name': '夏目漱石'}, {'author__name': '宮沢賢治'}]>
Book.objects.values_list("author__name") = <QuerySet [('夏目漱石',), ('夏目漱石',), ('宮沢賢治',)]>
### 4.3 annotate() + F()オブジェクト
Book.objects.annotate(
author_name=F("author__name")
).values("author_name") = <QuerySet [{'author_name': '夏目漱石'}, {'author_name': '夏目漱石'}, {'author_name': '宮沢賢治'}]>
### 4.4 aggregate() / annotate() + Avg
Author.objects.aggregate(avg_price=Avg("book__price")) = {'avg_price': 620.0}
Author.objects.annotate(
avg_price=Avg("book__price")
).values("avg_price") = <QuerySet [{'avg_price': 555.0}, {'avg_price': 750.0}]>
以上