クエリ式 (query expression) は、update、create、filter、order by、annotation、またはaggregateの一部として使用できる値または計算を記述します。式が真偽値を出力する場合、フィルタで直接使用できます。クエリの記述に使用できる組み込みの式 (expression) は多数あります (以下で説明します)。式を組み合わせたり、場合によっては入れ子にしてより複雑な計算を行うこともできます。
Django は Python の定数や変数、そして他の式を使って、クエリ式での否定、 加算、減算、乗算、除算、モジュロ演算、そしてべき乗演算をサポートします。
output_field)¶このセクションで説明する式の多くは、オプションの output_field パラメータをサポートしています。指定された場合、 Django はデータベースから値を取得した後、そのフィールドに値を読み込みます。
output_field は IntegerField() や BooleanField() のようなモデルフィールドのインスタンスを取ります。通常、フィールドには max_length のような引数は不要です。フィールドの引数はデータの検証に関連しており、その検証は式の出力値に対して実行されないからです。
output_field が必要なのは、フィールド型が混在する複雑な式のように、 Django が結果のフィールド型を自動的に判断できない場合だけです。例えば、 DecimalField() と FloatField() を足す場合、 output_field=FloatField() のように出力フィールドが必要になります。
>>> from django.db.models import Count, F, Value
>>> from django.db.models.functions import Length, Upper
>>> from django.db.models.lookups import GreaterThan
# Find companies that have more employees than chairs.
>>> Company.objects.filter(num_employees__gt=F("num_chairs"))
# Find companies that have at least twice as many employees
# as chairs. Both the querysets below are equivalent.
>>> Company.objects.filter(num_employees__gt=F("num_chairs") * 2)
>>> Company.objects.filter(num_employees__gt=F("num_chairs") + F("num_chairs"))
# How many chairs are needed for each company to seat all employees?
>>> company = (
... Company.objects.filter(num_employees__gt=F("num_chairs"))
... .annotate(chairs_needed=F("num_employees") - F("num_chairs"))
... .first()
... )
>>> company.num_employees
120
>>> company.num_chairs
50
>>> company.chairs_needed
70
# Create a new company using expressions.
>>> company = Company.objects.create(name="Google", ticker=Upper(Value("goog")))
# Be sure to refresh it if you need to access the field.
>>> company.refresh_from_db()
>>> company.ticker
'GOOG'
# Annotate models with an aggregated value. Both forms
# below are equivalent.
>>> Company.objects.annotate(num_products=Count("products"))
>>> Company.objects.annotate(num_products=Count(F("products")))
# Aggregates can contain complex computations also
>>> Company.objects.annotate(num_offerings=Count(F("products") + F("services")))
# Expressions can also be used in order_by(), either directly
>>> Company.objects.order_by(Length("name").asc())
>>> Company.objects.order_by(Length("name").desc())
# or using the double underscore lookup syntax.
>>> from django.db.models import CharField
>>> from django.db.models.functions import Length
>>> CharField.register_lookup(Length)
>>> Company.objects.order_by("name__length")
# Boolean expression can be used directly in filters.
>>> from django.db.models import Exists, OuterRef
>>> Company.objects.filter(
... Exists(Employee.objects.filter(company=OuterRef("pk"), salary__gt=10))
... )
# Lookup expressions can also be used directly in filters
>>> Company.objects.filter(GreaterThan(F("num_employees"), F("num_chairs")))
# or annotations.
>>> Company.objects.annotate(
... need_chairs=GreaterThan(F("num_employees"), F("num_chairs")),
... )
注釈
これらの式は django.db.models.expressions と django.db.models.aggregates で定義されていますが、利便性のために django.db.models からインポートできます。
F() 式¶F() オブジェクトはモデルフィールドの値、モデルフィールドを変換した値、アノテーションを付けた列を表します。これを使うことで、実際にデータベースから Python のメモリに取り出さずに、モデルフィールドの値を参照してデータベース操作を行うことができます。
代わりに、 Django は F() オブジェクトを使って、データベースレベルで必要な操作を記述する SQL 式を生成します。
例でやってみましょう。通常はこのようにします:
# Tintin filed a news story!
reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed += 1
reporter.save()
ここでは、reporter.stories_filed の値をデータベースからメモリに取り込んで、Pythonでおなじみの演算子を使って操作し、そのオブジェクトをデータベースに保存しています。しかし、代わりに次のようにすることもできます:
from django.db.models import F
reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed = F("stories_filed") + 1
reporter.save()
reporter.stories_filed = F('stories_filed') + 1 はインスタンス属性に値を代入する普通の Python 構文のように見えますが、実際にはデータベースに対する操作を記述するSQL構文です。
Djangoが F() インスタンスを見つけると、標準のPython演算子をオーバーライドしてカプセル化されたSQL式を作成します。この場合、reporter.stories_filed によって表されるデータベースフィールドをインクリメントするようにデータベースに指示する式が作成されます。
reporter.stories_filed にどんな値があったとしても、Pythonはそれを知ることはありません。それは完全にデータベースが扱います。Python が Django の F() クラスを通して行うことは、フィールドを参照し、処理を記述する SQL 構文を作成することだけです。
このようにして保存された新しい値にアクセスするには、オブジェクトをリロードする必要があります:
reporter = Reporters.objects.get(pk=reporter.pk)
# Or, more succinctly:
reporter.refresh_from_db()
As well as being used in operations on single instances as above, F() can
be used with update() to perform bulk updates on a QuerySet. This
reduces the two queries we were using above - the get() and the
save() - to just one:
reporter = Reporters.objects.filter(name="Tintin")
reporter.update(stories_filed=F("stories_filed") + 1)
また、 update() を使用して複数のオブジェクトのフィールド値をインクリメントすることもできます。これは、データベースからPythonに全てを引き出し、それら全てをループ処理し、各オブジェクトのフィールド値をインクリメントし、そして各オブジェクトをデータベースに保存するよりも、はるかに高速になる可能性があります:
Reporter.objects.update(stories_filed=F("stories_filed") + 1)
つまり、F() は、以下のようなパフォーマンス上の利点があります:
作業をPythonではなく、データベースに行わせること
一部の操作に必要なクエリの数を減らすこと
F() 式のスライス¶文字列ベースのフィールド、テキストベースのフィールド、および ArrayField では、Python の配列スライス構文が使用できます。インデックスは0から始まり、slice の step 引数はサポートされていません。例えば、次のように記述できます:
>>> # Replacing a name with a substring of itself.
>>> writer = Writers.objects.get(name="Priyansh")
>>> writer.name = F("name")[1:5]
>>> writer.save()
>>> writer.refresh_from_db()
>>> writer.name
'riya'
F() を使った競合状態の回避¶F() のもう一つの便利な利点は、Pythonではなくデータベースがフィールドの値を更新することで 競合状態 を避けることができるということです。
2つのPythonスレッドが上記の最初の例のコードを実行した場合、1つのスレッドはもう1つのスレッドがデータベースから値を取得した後にフィールドの値を取得、インクリメント、保存する可能性があります。2番目のスレッドが保存する値は元の値に基づいています。
データベースがフィールドの更新を担当する場合、処理はより堅牢になります。 save() や update() が実行されたときに、インスタンスが取得されたときの値ではなく、データベースのフィールドの値だけに基づいてフィールドが更新されます。
F() への代入は Model.save() の後にも残ります¶モデルフィールドに割り当てられた F() オブジェクトはモデルインスタンスを保存した後も保持され、 save() のたびに適用されます。例えば:
reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed = F("stories_filed") + 1
reporter.save()
reporter.name = "Tintin Jr."
reporter.save()
この場合、 stories_filed は2回更新されます。初期値が 1 であれば、最終的な値は 3 になります。このような永続化は、例えば refresh_from_db() を使ってモデルオブジェクトを保存した後にリロードすることで回避できます。
F() を使う¶F() は QuerySet フィルタでも非常に有用で、Python の値ではなく、フィールドの値に基づいてオブジェクトをフィルタリングできます。
これは クエリで F() 式を使う で説明されています。
F() をアノテーションと一緒に使う¶F() を使うと、算術演算で異なるフィールドを組み合わせてモデルに動的なフィールドを作成できます:
company = Company.objects.annotate(chairs_needed=F("num_employees") - F("num_chairs"))
組み合わせるフィールドの型が異なる場合、どのようなフィールドが返されるかを Django に伝える必要があります。ほとんどの式は output_field をサポートしていますが、 F() はサポートしていないので、 ExpressionWrapper で式をラップする必要があります:
from django.db.models import DateTimeField, ExpressionWrapper, F
Ticket.objects.annotate(
expires=ExpressionWrapper(
F("active_at") + F("duration"), output_field=DateTimeField()
)
)
ForeignKey のようなリレーション先フィールドを参照する場合、 F() はモデルインスタンスではなくプライマリキーの値を返します:
>>> car = Company.objects.annotate(built_by=F("manufacturer"))[0]
>>> car.manufacturer
<Manufacturer: Toyota>
>>> car.built_by
3
F() を使う¶F() と Expression.asc() または desc() のキーワード引数 nulls_first または nulls_last を使用して、フィールドの null 値のソートの順序を制御します。デフォルトでは、ソートの順序はデータベースに依存します。
例えば、まだコンタクトしていない企業 (last_contacted がNULL) をコンタクトした企業の後に並べるには次のようにします:
from django.db.models import F
Company.objects.order_by(F("last_contacted").desc(nulls_last=True))
F() を使う¶BooleanField を出力する F() 式は反転演算子 ~F() で論理的に否定することができます。たとえば企業の is_active ステータスを反転する場合、以下のようにします:
from django.db.models import F
Company.objects.update(is_active=~F("is_active"))
Func() 式¶Func() 式は COALESCE や LOWER のようなデータベース関数や、 SUM のような集計を含むすべての式の基本型です。これらは直接使用できます:
from django.db.models import F, Func
queryset.annotate(field_lower=Func(F("field"), function="LOWER"))
または、データベース関数のライブラリを構築するためにも使用できます:
class Lower(Func):
function = "LOWER"
queryset.annotate(field_lower=Lower("field"))
しかし、どちらの場合も、モデルが追加の属性 field_lower でアノテーションされたクエリセットを生成します。この属性は、大まかには次のSQLから生成されます:
SELECT
...
LOWER("db_table"."field") as "field_lower"
組み込みのデータベース関数の一覧は データベース関数 を参照してください。
Func のAPI は以下の通りです:
生成する関数を指定するクラス属性です。具体的には、 function が template 内の function プレースホルダとして補間されます。デフォルトは None です。
この関数に対して生成される SQL を記述する、フォーマット文字列としてのクラス属性です。デフォルトは '%(function)s(%(expressions)s)' です。
strftime('%W','date') のようなSQLを作成していて、クエリでリテラル文字 % が必要な場合は、template 属性では %%%% と4倍にしてください。文字列は as_sql() でのテンプレート補間と、データベースカーソルでのクエリパラメータによるSQL補間の2回補間されるからです。
クラス属性は expressions のリストを結合するために使用される文字を表します。デフォルトは ', ' です。
関数が受け付ける引数の数を表すクラス属性です。この属性が設定されていて、関数が異なる数の式で呼び出された場合、 TypeError が発生します。デフォルトは None です。
データベース関数の SQL フラグメントを生成します。タプル (sql, params) を返します。sql は SQL 文字列で、params はクエリパラメータのリストまたはタプルです。
as_vendor() メソッドは、function, template, arg_joiner およびその他の **extra_context パラメータを使用して、必要に応じてSQLをカスタマイズするべきです。例えば:
django/db/models/functions.py¶class ConcatPair(Func):
...
function = "CONCAT"
...
def as_mysql(self, compiler, connection, **extra_context):
return super().as_sql(
compiler,
connection,
function="CONCAT_WS",
template="%(function)s('', %(expressions)s)",
**extra_context
)
SQL インジェクションの脆弱性を回避するために、extra_context は 信頼できないユーザー入力を含んではいけません 。これらの値はデータベースドライバによってエスケープされるクエリパラメータとして渡されるのではなく、SQL文字列に補間されるからです。
引数 *expressions は関数が適用される位置引数としての式のリストです。式は文字列に変換され、 arg_joiner で連結された後、 expressions プレースホルダとして template に挿入されます。
位置引数には式か Python の値を指定できます。文字列はカラム参照とみなされ、 F() 式でラップされ、その他の値は Value() 式でラップされます。
*extra kwargs は template 属性に補間できる key=value のペアです。SQL インジェクションの脆弱性を回避するために、*extra は 信頼できないユーザー入力を含んではいけません 。これらの値はデータベースドライバによってエスケープされるクエリパラメータとして渡されるのではなく、SQL文字列に補間されるからです。
function, template, および arg_joiner キーワードは、同じ名前の属性を置き換えるために使用でき、独自のクラスを定義する必要はありません。output_field は、期待される戻り値の型を定義するために使用できます。
Aggregate() 式¶集計式 (aggregate expression) は Func() 式 の特殊なケースで、 GROUP BY 句が必要であることをクエリに通知します。 Sum() や Count() などの 集計関数 はすべて Aggregate() を継承しています。
Aggregate は式であり、ラップ式でもあるので、複雑な計算を表現できます:
from django.db.models import Count
Company.objects.annotate(
managers_required=(Count("num_employees") / 4) + Count("num_managers")
)
Aggregate の API は以下の通りです:
この aggregate 用に生成される SQL を記述する、フォーマット文字列としてのクラス属性です。デフォルトは '%(function)s(%(distinct)s%(expressions)s)' です。
生成される集計関数を記述するクラス属性です。具体的には、 function が template 内の function プレースホルダとして補間されます。デフォルトは None です。
この集計関数が distinct キーワード引数を渡すことができるかどうかを決定するクラス属性。 False (デフォルト) に設定されている場合、 distinct=True が渡されると TypeError が発生します。
ほとんどの集計関数は、空の結果セットに適用すると NULL となるため、デフォルトは None です。
位置引数 expressions には、式、モデルフィールドのトランスフォーム、またはモデルフィールドの名前を指定できます。これらは文字列に変換され、 template 内の expressions プレースホルダとして使用されます。
引数 distinct は、集計関数を 式 (または複数の 式 の場合は値の集合) ごとに呼び出すかどうかを決定します。この引数は allow_distinct が True に設定されている集計でのみサポートされます。
引数 filter は Q オブジェクト を取り、集計する行をフィルタリングします。使用例については 条件付きの集計 と アノテーションのフィルタリング を参照してください。
引数 default は Coalesce に集計句と一緒に渡される値を取ります。これは、クエリセット(またはグループ化)にエントリがない場合に None 以外の値を返すように指定するのに便利です。
この **extra キーワード引数は template 属性に補間できる key=value のペアです。
独自の集計関数を作成することもできます。最低限 function を定義する必要がありますが、生成される SQL を完全にカスタマイズすることもできます。以下に簡単な例を示します:
from django.db.models import Aggregate
class Sum(Aggregate):
# Supports SUM(ALL field).
function = "SUM"
template = "%(function)s(%(all_values)s%(expressions)s)"
allow_distinct = False
arity = 1
def __init__(self, expression, all_values=False, **extra):
super().__init__(expression, all_values="ALL " if all_values else "", **extra)
Value() 式¶Value() オブジェクトは式の最小の構成要素、つまり単純な値を表します。式の中で整数、真偽値、文字列の値を表す必要がある場合、その値を Value() で囲みます。
Value() を直接使用することはほとんどありません。 F('field') + 1 という式を書くと、Djangoは暗黙のうちに 1 を Value() でラップし、単純な値をより複雑な式で使用できるようにします。式に文字列を渡したいときは Value() を使用する必要があります。ほとんどの式では、文字列引数をフィールド名として解釈します。例えば Lower('name') のように。
引数 value には、 1, True, None など、式に含める値を記述します。Django はこれらの Python の値を対応するデータベース型に変換する方法を知っています。
output_field が指定されていない場合、多くの一般的な型では value の型から推測されます。例えば、 datetime.datetime のインスタンスを value として渡すと、 output_field のデフォルトは DateTimeField です。
ExpressionWrapper() 式¶ExpressionWrapper は他の式を囲み、 output_field のような他の式では利用できないプロパティへのアクセスを提供します。 ExpressionWrapper は F() をアノテーションと一緒に使う で説明されているように、異なる型を持つ F() 式で算術演算を行う場合に必要です。
条件式を用いると、if ... elif ... else ロジックをクエリーの中で使用できるようになります。Django はネイティブで SQL CASE 式をサポートしています。詳細については 条件式 を読んでください。
Subquery() 式¶Subquery 式を使うと、 QuerySet に明示的にサブクエリを追加できます。
たとえば、各投稿にその投稿の最新コメントの投稿者のメールアドレスをアノテーションとして付けることができます:
>>> from django.db.models import OuterRef, Subquery
>>> newest = Comment.objects.filter(post=OuterRef("pk")).order_by("-created_at")
>>> Post.objects.annotate(newest_commenter_email=Subquery(newest.values("email")[:1]))
PostgreSQLの場合、SQLは以下のようになります:
SELECT "post"."id", (
SELECT U0."email"
FROM "comment" U0
WHERE U0."post_id" = ("post"."id")
ORDER BY U0."created_at" DESC LIMIT 1
) AS "newest_commenter_email" FROM "post"
注釈
この節の例は、Django にサブクエリを強制する方法を示すものです。場合によっては、同じタスクをより明確に、あるいは効率的に実行する等価なクエリセットを書けるかもしれません。
OuterRef は Subquery 内のクエリセットが外側のクエリまたはそのトランスフォームのフィールドから参照される必要がある場合に使用します。これは F 式と同じように動作しますが、有効なフィールドを参照しているかどうかのチェックは外側のクエリセットが解決されるまで行われません。
OuterRef のインスタンスは、入れ子になった Subquery のインスタンスと一緒に使用できます。例えば、このクエリセットを正しく解決するには、 Subquery インスタンスの入れ子になったペア内にある必要があります:
>>> Book.objects.filter(author=OuterRef(OuterRef("pk")))
Subquery を __in ルックアップの対象として使う場合など、Subquery から単一のカラムを返さなければならないことがあります。たとえば、直近一日以内に公開された投稿のすべてのコメントを返す場合:
>>> from datetime import timedelta
>>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1)
>>> posts = Post.objects.filter(published_at__gte=one_day_ago)
>>> Comment.objects.filter(post__in=Subquery(posts.values("pk")))
この場合、サブクエリは values() を使って単一のカラム、つまり投稿のプライマリキーだけを返さなければなりません。
サブクエリが複数行を返すのを防ぐには、クエリセットのスライス ([:1]) を使用します:
>>> subquery = Subquery(newest.values("email")[:1])
>>> Post.objects.annotate(newest_commenter_email=subquery)
この場合、サブクエリは単一のカラムの単一の行、つまり最近作成されたコメントのメールアドレスだけを返す必要があります。
(スライスの代わりに get() を使うと、 Subquery 内でクエリセットが使われるまで OuterRef が解決できないため失敗します)
Exists() サブクエリ¶Exists は SQL の EXISTS ステートメントを使う サブクエリ のサブクラスです。多くの場合、最初にマッチする行が見つかった時点でデータベースがサブクエリの評価を停止できるので、サブクエリよりも性能が良くなります。
たとえば、各投稿に直近1日以内のコメントがあるかどうかのアノテーションを付けることができます:
>>> from django.db.models import Exists, OuterRef
>>> from datetime import timedelta
>>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1)
>>> recent_comments = Comment.objects.filter(
... post=OuterRef("pk"),
... created_at__gte=one_day_ago,
... )
>>> Post.objects.annotate(recent_comment=Exists(recent_comments))
PostgreSQLの場合、SQLは以下のようになります:
SELECT "post"."id", "post"."published_at", EXISTS(
SELECT (1) as "a"
FROM "comment" U0
WHERE (
U0."created_at" >= YYYY-MM-DD HH:MM:SS AND
U0."post_id" = "post"."id"
)
LIMIT 1
) AS "recent_comment" FROM "post"
Exists に単一のカラムの参照を強制する必要はありません。列は破棄され、真偽値の結果が返されるからです。同様に、ソートは SQL の EXISTS サブクエリ内では重要ではなく、パフォーマンスを低下させるだけなので、自動的に破棄されます。
~Exists() を使って NOT EXISTS をクエリできます。
Subquery() または Exists() 式におけるフィルタリング¶真偽値を返す Subquery() と Exists() は When 式で condition として使用したり、クエリセットに直接フィルタをかけたりすることができます:
>>> recent_comments = Comment.objects.filter(...) # From above
>>> Post.objects.filter(Exists(recent_comments))
これにより、サブクエリが SELECT カラムに追加されなくなり、パフォーマンスが向上する可能性があります。
Subquery 式の中で集計 (aggregate) を使う¶集計 (aggregate) は Subquery の中で使用できますが、サブクエリのグループ化を正しく行うためには filter(), values(), annotate() の特定の組み合わせを必要とします。
両方のモデルに length フィールドがあると仮定すると、投稿の長さがすべてのコメントの長さの合計よりも大きい投稿を見つけることができます:
>>> from django.db.models import OuterRef, Subquery, Sum
>>> comments = Comment.objects.filter(post=OuterRef("pk")).order_by().values("post")
>>> total_comments = comments.annotate(total=Sum("length")).values("total")
>>> Post.objects.filter(length__gt=Subquery(total_comments))
最初の filter(...) はサブクエリを関連するパラメータに制限します。 order_by() は Comment モデルのデフォルトの ordering (もしあれば) を削除します。 value('post') はコメントを Post で集計します。最後に、 annotate(...) が集計を行います。これらのクエリセットメソッドを適用する順番は重要です。この場合、サブクエリは1つのカラムに限定する必要があるため、 values('total') が必要となります。
これは Subquery 内で集計を行う唯一の方法です。 aggregate() を使用すると、クエリセットを評価しようとします(そして OuterRef がある場合、これを解決することはできません)。
データベース式では複雑な WHERE 句を簡単に表現できないことがあります。このような場合は RawSQL 式を使用してください。たとえば:
>>> from django.db.models.expressions import RawSQL
>>> queryset.annotate(val=RawSQL("select col from sometable where othercol = %s", (param,)))
このような特別なルックアップは、(明示的にSQLコードを記述しているため)異なるデータベースエンジンに移植できない可能性があり、DRY原則に反するので、可能なら避けるべきです。
RawSQL 式は __in フィルタの対象としても使用できます:
>>> queryset.filter(id__in=RawSQL("select id from sometable where col = %s", (param,)))
警告
SQLインジェクション攻撃 を防ぐために、 params を使って、ユーザが操作できるパラメータをエスケープする必要があります。 params は必須引数で、ユーザが入力したデータでSQLを補間しないことを強制するためのものです。
また、SQL文字列のプレースホルダを引用符で囲んではいけません。 この例では %s を引用符で囲んでいるため、SQLインジェクションの脆弱性があります:
RawSQL("select col from sometable where othercol = '%s'") # unsafe!
Django が SQL インジェクションを防ぐ 仕組みの説明があります。
ウィンドウ関数は、パーティション上で関数を適用する方法を提供します。通常の集計関数が group by で定義された各セットに対して最終結果を計算するのに対し、ウィンドウ関数は フレーム とパーティション上で操作し、各行に対して結果を計算します。
同じクエリ内で複数のウィンドウを指定することができ、Django ORM では QuerySet.annotate() 呼び出しに複数の式を含めることで実現します。ORMは名前付きウィンドウを使用しませんが、代わりに選択されたカラムの一部となります。
デフォルトは %(expression)s OVER (%(window)s) です。引数 expression だけを指定した場合、window 句は空白になります。
Window クラスは OVER 句のメインの式です。
引数の expression は ウィンドウ関数, 集計関数, またはwindow句で互換性のある式です。
partition_by 引数には、行のパーティショニングを制御する式または一連の式 (カラム名は F オブジェクトで囲む必要があります) を指定します。パーティショニングは、結果セットを計算するために使用する行を絞り込みます。
output_field は引数か式で指定します。
order_by 引数には asc() と desc() を呼び出せる式、フィールド名の文字列 (オプションで降順を示す "-" をプレフィックスに付けられます)、または文字列や式のタプルやリストを渡すことができます。順序は式が適用される順番を制御します。例えば、パーティション内の行を合計した場合、最初の結果は最初の行の値になり、2番目は最初の行と2番目の行の合計になります。
パラメータ frame は、計算に使用する他の行を指定します。詳細は フレーム を参照してください。
たとえば、各映画に同じスタジオによる同じジャンル・公開年の映画の平均レーティングでアノテーションを付ける場合:
>>> from django.db.models import Avg, F, Window
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... partition_by=[F("studio"), F("genre")],
... order_by="released__year",
... ),
... )
これにより、映画の評価が他の映画より高いか低いかをチェックできます。
同じウィンドウ、つまり同じパーティションとフレームに複数の式を適用したい場合があります。例えば、同じクエリで3つのウィンドウ関数を使用することで、各映画のグループ(同じスタジオ、ジャンル、リリース年)のベストとワーストの評価も含めるように、前の例を修正できます。前の例のパーティションとソートは、繰り返しを減らすために辞書に抽出されます:
>>> from django.db.models import Avg, F, Max, Min, Window
>>> window = {
... "partition_by": [F("studio"), F("genre")],
... "order_by": "released__year",
... }
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... **window,
... ),
... best=Window(
... expression=Max("rating"),
... **window,
... ),
... worst=Window(
... expression=Min("rating"),
... **window,
... ),
... )
ウィンドウ関数に対するフィルタリングは、ルックアップが分離型でない(コネクタとして OR や XOR を使用していない)限り、集計を実行するクエリセットに対してもサポートされます。
たとえば、集計に依存し、ウィンドウ関数とフィールドに対して OR でフィルタされたクエリはサポートされていません。集計後に真偽値を返す関数を適用すると、通常はグループから除外されるはずの行が含まれる可能性があります:
>>> qs = Movie.objects.annotate(
... category_rank=Window(Rank(), partition_by="category", order_by="-rating"),
... scenes_count=Count("actors"),
... ).filter(Q(category_rank__lte=3) | Q(title__contains="Batman"))
>>> list(qs)
NotImplementedError: Heterogeneous disjunctive predicates against window functions
are not implemented when performing conditional aggregation.
Django に組み込みのデータベースバックエンドでは、MySQL、 PostgreSQL、 Oracle がウィンドウ表現をサポートしています。ウィンドウ表現機能のサポートはデータベースによって異なります。例えば、 asc() や desc() のオプションはサポートされていないかもしれません。必要に応じてデータベースのドキュメントを参照してください。
ウィンドウフレームでは、範囲ベースの行のシーケンスか、通常の行のシーケンスのいずれかを選択できます。
この属性は 'RANGE' に設定されています。
PostgreSQLは ValueRange のサポートに制限があり、 CURRENT ROW や UNBOUNDED FOLLOWING のような標準的な開始点と終了点の使用しかサポートしていません。
exclusion 引数が追加されました。
この属性は 'ROWS' に設定されています。
exclusion 引数が追加されました。
どちらのクラスもテンプレートとともにSQLを返します:
%(frame_type)s BETWEEN %(start)s AND %(end)s
exclusion 引数は、サポートされているデータベースでウィンドウフレームから行(CURRENT_ROW)、グループ(GROUP)、および同点(TIES)を除外するために使用できます。
%(frame_type)s BETWEEN %(start)s AND %(end)s EXCLUDE %(exclusion)s
フレームは、結果を計算するために使用される行を絞り込みます。ある開始点から指定された終了点までシフトします。フレームはパーティションの有無に関係なく使用できますが、決定論的な結果を得るためには、ウィンドウの順序を指定することを推奨します。フレームでは、フレーム内のピアは等価値を持つ行であり、ソート句がない場合はすべての行です。
フレームのデフォルトの開始点は UNBOUNDED PRECEDING で、パーティションの最初の行になります。終了点は ORM が生成する SQL に常に明示的に含まれ、デフォルトでは UNBOUNDED FOLLOWING です。デフォルトのフレームは、パーティションからセットの最後の行までの全ての行を含みます。
start および end 引数に受け入れられる値は、None、整数、またはゼロです。start に負の整数を指定するとN行前 N PRECEDING となり、None は先頭行 UNBOUNDED PRECEDING となります。ROWS モードでは、start に正の整数を指定するとN行後 N FOLLOWING となります。end には正の整数も受け入れられ、これによりN行後 N FOLLOWING となります。ROWS モードでは、end に負の整数を指定するとN行前 N PRECEDING となります。start と end の両方でゼロを指定すると現在の行 CURRENT ROW が返されます。
CURRENT ROW が含むものには違いがあります。 ROWS モードで指定された場合、フレームは現在の行から開始または終了します。 RANGE モードで指定された場合、フレームはソートの順序に従って最初か最後のピアから開始または終了します。 したがって、 RANGE CURRENT ROW は、ソートで指定された同じ値を持つ行に対して式を評価します。テンプレートは start と end の両方を含むので、これは次のように表現できます:
ValueRange(start=0, end=0)
映画の "ピア" が同じ年に同じジャンルで同じスタジオからリリースされた映画として記述されている場合、この RowRange の例では、映画の2つ前から2つ後のピアの平均評価で各映画にアノテーションを付けます:
>>> from django.db.models import Avg, F, RowRange, Window
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... partition_by=[F("studio"), F("genre")],
... order_by="released__year",
... frame=RowRange(start=-2, end=2),
... ),
... )
データベースがサポートしていれば、パーティション内の式の値に基づいて開始点と終了点を指定することもできます。もし Movie モデルの released フィールドが各映画の公開月を保存している場合、この ValueRange の例では、各映画の12ヶ月前から12ヶ月後に公開された映画の同業者の平均レーティングでアノテーションを付けます:
>>> from django.db.models import Avg, F, ValueRange, Window
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... partition_by=[F("studio"), F("genre")],
... order_by="released__year",
... frame=ValueRange(start=-12, end=12),
... ),
... )
RowRange に対して、正の整数の start と負の整数の end のサポートが追加されました。
以下に、ライブラリの作者に役立つ技術的な実装の詳細を示します。以下の技術的な API と例は、 Django が提供する組み込み機能を拡張する汎用的なクエリ式を作成するのに役立ちます。
クエリ式は クエリ式 (expression) API を実装していますが、以下のようなメソッドや属性も公開しています。すべてのクエリ式は Expression() または関連するサブクラスを継承しなければなりません。
クエリ式が別の式をラップする場合、ラップされた式の適切なメソッドを呼び出す責任があります。
この式が Field.db_default で使えることを Django に伝えます。デフォルトは False です。
この式が制約の検証中に使用できることを Django に教えます。constraint_validation_compatible が False に設定された式は、ソース式を1つだけ持つ必要があります。デフォルトでは True です。
Django に、この式が集計を含んでいて、クエリに GROUP BY 句を追加する必要があることを伝えます。
この式が Window 式を含んでいることを Django に伝えます。これは例えば、データを変更するクエリでウィンドウ関数式を許可しないようにするのに使います。
この式が QuerySet.filter() で参照できることを Django に伝えます。デフォルトは True です。
Django に、式が空の結果セットに対して関数を適用する際にどの値を返すべきかを 指示します。デフォルトは NotImplemented で、式はデータベースで計算されます。
Tells Django that this expression contains a set-returning function,
enforcing subquery evaluation. It's used, for example, to allow some
Postgres set-returning functions (e.g. JSONB_PATH_QUERY,
UNNEST, etc.) to skip optimization and be properly evaluated when
annotations spawn rows themselves. Defaults to False.
Tells Django that this expression allows composite expressions, for
example, to support composite primary keys. Defaults to False.
クエリに追加する前に、式の前処理や検証を行う機会を提供します。 resolve_expression() は、ネストした式に対しても必ず呼び出されなければなりません。 self の copy() は、必要な変換を行ったものを返すべきです。
query はバックエンドクエリの実装です。
allow_joins はクエリで結合を使用するかどうかを指定する真偽値です。
reuse は複数結合シナリオのための再利用可能な結合のセットです。
summarize は真偽値で、True の場合、計算中のクエリが終端の集計クエリであることを示します。
for_save は真偽値で、 True の場合、実行中のクエリが作成または更新を行うことを示します。
内部式の順序ありリストを返します。例えば:
>>> Sum(F("foo")).get_source_expressions()
[F('foo')]
式のリストを受け取り、それを get_source_expressions() が返せるように格納します。
self のクローン(コピー)を返し、すべてのカラムエイリアスが再ラベル付けされます。カラムエイリアスは、サブクエリが作成される際に名前が変更されます。relabeled_clone() は、任意のネストされた式にも呼び出され、クローンに割り当てられるべきです。
change_map は古いエイリアスを新しいエイリアスにマッピングする辞書です。
実装例:
def relabeled_clone(self, change_map):
clone = copy.copy(self)
clone.expression = self.expression.relabeled_clone(change_map)
return clone
式が value をより適切な型に強制するためのフック。
expression は self と同様です。
この式によって参照されるカラムのリストを返す責任があります。get_group_by_cols() は、任意のネストされた式に対して呼び出されるべきです。特に、F() オブジェクトはカラムへの参照を保持しています。
昇順でソートする準備が整った式を返します。
nulls_first と nulls_last は、null値がどのようにソートされるかを定義します。使用例については、NULL 値のソートに F() を使う を参照してください。
降順にソートする準備が整った式を返します。
nulls_first と nulls_last は、null値がどのようにソートされるかを定義します。使用例については、NULL 値のソートに F() を使う を参照してください。
他のクエリ式を使用したり、他のクエリ式と統合したりする独自のクエリ式クラスを書くことができます。組み込みの Func() 式 を使用せずに COALESCE SQL関数の実装を書く例を見てみましょう。
SQL 関数 COALESCE はカラムや値のリストを受け取るように定義されています。この関数は NULL でない最初のカラムまたは値を返します。
まずはSQL生成に使用するテンプレートと、いくつかの属性を設定する __init__() メソッドを定義します:
import copy
from django.db.models import Expression
class Coalesce(Expression):
template = "COALESCE( %(expressions)s )"
def __init__(self, expressions, output_field):
super().__init__(output_field=output_field)
if len(expressions) < 2:
raise ValueError("expressions must have at least 2 elements")
for expression in expressions:
if not hasattr(expression, "resolve_expression"):
raise TypeError("%r is not an Expression" % expression)
self.expressions = expressions
少なくとも 2 つのカラムまたは値を必要とし、式であることを確認するなど、パラメータの基本的な検証を行います。ここで output_field を要求しているのは、 Django が最終的な結果をどのようなモデルフィールドに代入すべきかを知るためです。
前処理とバリデーションを実装します。この時点では独自のバリデーションを行っていないので、ネストした式に委譲します:
def resolve_expression(
self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
):
c = self.copy()
c.is_summary = summarize
for pos, expression in enumerate(self.expressions):
c.expressions[pos] = expression.resolve_expression(
query, allow_joins, reuse, summarize, for_save
)
return c
次に、SQLを生成するメソッドを書きます:
def as_sql(self, compiler, connection, template=None):
sql_expressions, sql_params = [], []
for expression in self.expressions:
sql, params = compiler.compile(expression)
sql_expressions.append(sql)
sql_params.extend(params)
template = template or self.template
data = {"expressions": ",".join(sql_expressions)}
return template % data, sql_params
def as_oracle(self, compiler, connection):
"""
Example of vendor specific handling (Oracle in this case).
Let's make the function name lowercase.
"""
return self.as_sql(compiler, connection, template="coalesce( %(expressions)s )")
as_sql() メソッドはカスタムキーワード引数をサポート可能で、これにより as_vendorname() メソッドがSQL文字列を生成するためのデータをオーバーライドできます。as_vendorname() メソッド内で self を変更するよりも、as_sql() キーワード引数を使用してカスタマイズする方が好ましいです。前者は異なるデータベースバックエンドで実行する際にエラーを引き起こす可能性があるためです。クラスがクラス属性に依存してデータを定義している場合は、as_sql() メソッドでオーバーライドを許可することを検討してください。
compiler.compile() メソッドを使って各 expressions のSQLを生成し、その結果をカンマで連結します。そして、テンプレートにデータを入力し、SQLとパラメータを返します。
また、Oracle バックエンドに特化したカスタム実装も定義しました。Oracle バックエンドが使用されている場合は、 as_sql() の代わりに as_oracle() 関数が呼び出されます。
最後に、クエリ式が他のクエリ式と協調できるようにするためのメソッドを実装します:
def get_source_expressions(self):
return self.expressions
def set_source_expressions(self, expressions):
self.expressions = expressions
どう動作するか見てみましょう:
>>> from django.db.models import F, Value, CharField
>>> qs = Company.objects.annotate(
... tagline=Coalesce(
... [F("motto"), F("ticker_name"), F("description"), Value("No Tagline")],
... output_field=CharField(),
... )
... )
>>> for c in qs:
... print("%s: %s" % (c.name, c.tagline))
...
Google: Do No Evil
Apple: AAPL
Yahoo: Internet Company
Django Software Foundation: No Tagline
Func の __init__() (**extra) および as_sql() (**extra_context) のキーワード引数は、 (データベースドライバがエスケープするような) クエリパラメータとして渡されるのではなく、SQL 文字列に補間されるので、信頼できないユーザ入力を含んではいけません。
例えば、substring がユーザーが入力したものである場合、この関数は SQL インジェクションに対して脆弱です:
from django.db.models import Func
class Position(Func):
function = "POSITION"
template = "%(function)s('%(substring)s' in %(expressions)s)"
def __init__(self, expression, substring):
# substring=substring is an SQL injection vulnerability!
super().__init__(expression, substring=substring)
この関数はパラメータなしで SQL 文字列を生成します。 substring はキーワード引数として super().__init__() に渡されるので、クエリがデータベースに送信される前に SQL 文字列に補間されます。
以下が修正後の実装です:
class Position(Func):
function = "POSITION"
arg_joiner = " IN "
def __init__(self, expression, substring):
super().__init__(substring, expression)
代わりに substring を位置引数として渡すと、データベースクエリのパラメータとして渡されます。
ある関数で異なる SQL 構文を使用するデータベースバックエンドを使用している場合は、 その関数のクラスに新しいメソッドを追加することで対応できます。
例えば Microsoft SQL Server 用のバックエンドを書くとしましょう。このバックエンドでは Length 関数に LENGTH の代わりに LEN という SQL を使用します。 as_sqlserver() という新しいメソッドを Length クラスに追加します:
from django.db.models.functions import Length
def sqlserver_length(self, compiler, connection):
return self.as_sql(compiler, connection, function="LEN")
Length.as_sqlserver = sqlserver_length
as_sql() の template パラメータを使って SQL をカスタマイズすることもできます。
django.db.connection.vendor がバックエンドに sqlserver を返すので、 as_sqlserver() を使用します。
サードパーティのバックエンドは、バックエンドパッケージのトップレベルの __init__.py ファイル、またはトップレベルの __init__.py からインポートされたトップレベルの expressions.py ファイル (またはパッケージ) に関数を登録できます。
使用しているバックエンドにパッチを当てたいユーザプロジェクトでは、このコードを AppConfig.ready() メソッドに記述します。
7月 02, 2025