クエリ式 (Query Expression)¶
クエリ式 (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() のように出力フィールドが必要になります。
output_field also allows using custom fields that perform type conversions
outside a specific model field context. For example, if you frequently need to
perform date arithmetic with timedelta, you can create a custom field that
handles the conversion, ensuring consistent results across databases. See
カスタムのモデルフィールドを作成する.
例¶
>>> 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")))
>>> 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 構文を作成することだけです。
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)
We can also use update() to increment
the field value on multiple objects - which could be very much faster than
pulling them all into Python from the database, looping over them, incrementing
the field value of each one, and saving each one back to the database:
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.name
'riya'
F() を使った競合状態の回避¶
F() のもう一つの便利な利点は、Pythonではなくデータベースがフィールドの値を更新することで 競合状態 を避けることができるということです。
2つのPythonスレッドが上記の最初の例のコードを実行した場合、1つのスレッドはもう1つのスレッドがデータベースから値を取得した後にフィールドの値を取得、インクリメント、保存する可能性があります。2番目のスレッドが保存する値は元の値に基づいています。
If the database is responsible for updating the field, the process is more
robust: it will only ever update the field based on the value of the field in
the database when the save() or update() is executed, rather
than based on its value when the instance was retrieved.
F() assignments are refreshed after Model.save()¶
F() objects assigned to model fields are refreshed from the database on
save() on backends that support it without incurring a subsequent
query (SQLite, PostgreSQL, and Oracle) and deferred otherwise (MySQL or
MariaDB). For example:
>>> reporter = Reporters.objects.get(name="Tintin")
>>> reporter.stories_filed = F("stories_filed") + 1
>>> reporter.save()
>>> reporter.stories_filed # This triggers a refresh query on MySQL/MariaDB.
14 # Assuming the database value was 13 when the object was saved.
In previous versions of Django, F() objects were not refreshed from the
database on save() which resulted in them being evaluated and
persisted every time the instance was saved.
フィルタで 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
NULL 値のソートに 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"
See データベース関数 for a list of built-in database functions.
Func のAPI は以下の通りです:
- class Func(*expressions, **extra)[ソース]¶
- function¶
生成する関数を指定するクラス属性です。具体的には、
functionがtemplate内のfunctionプレースホルダとして補間されます。デフォルトはNoneです。
- template¶
この関数に対して生成される SQL を記述する、フォーマット文字列としてのクラス属性です。デフォルトは
'%(function)s(%(expressions)s)'です。strftime('%W','date')のようなSQLを作成していて、クエリでリテラル文字%が必要な場合は、template属性では%%%%と4倍にしてください。文字列はas_sql()でのテンプレート補間と、データベースカーソルでのクエリパラメータによるSQL補間の2回補間されるからです。
- arg_joiner¶
クラス属性は
expressionsのリストを結合するために使用される文字を表します。デフォルトは', 'です。
- arity¶
関数が受け付ける引数の数を表すクラス属性です。この属性が設定されていて、関数が異なる数の式で呼び出された場合、
TypeErrorが発生します。デフォルトはNoneです。
- as_sql(compiler, connection, function=None, template=None, arg_joiner=None, **extra_context)[ソース]¶
データベース関数の 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 は以下の通りです:
- class Aggregate(*expressions, output_field=None, distinct=False, filter=None, default=None, order_by=None, **extra)[ソース]¶
- template¶
この aggregate 用に生成される SQL を記述する、フォーマット文字列としてのクラス属性です。デフォルトは
'%(function)s(%(distinct)s%(expressions)s)'です。
- function¶
生成される集計関数を記述するクラス属性です。具体的には、
functionがtemplate内のfunctionプレースホルダとして補間されます。デフォルトはNoneです。
- allow_distinct¶
この集計関数が
distinctキーワード引数を渡すことができるかどうかを決定するクラス属性。False(デフォルト) に設定されている場合、distinct=Trueが渡されるとTypeErrorが発生します。
- allow_order_by¶
- New in Django 6.0.
A class attribute determining whether or not this aggregate function allows passing a
order_bykeyword argument. If set toFalse(default),TypeErroris raised iforder_byis passed as a value other thanNone.
- empty_result_set_value¶
ほとんどの集計関数は、空の結果セットに適用すると
NULLとなるため、デフォルトはNoneです。
位置引数 expressions には、式、モデルフィールドのトランスフォーム、またはモデルフィールドの名前を指定できます。これらは文字列に変換され、 template 内の expressions プレースホルダとして使用されます。
引数 distinct は、集計関数を 式 (または複数の 式 の場合は値の集合) ごとに呼び出すかどうかを決定します。この引数は allow_distinct が True に設定されている集計でのみサポートされます。
引数 filter は Q オブジェクト を取り、集計する行をフィルタリングします。使用例については 条件付きの集計 と アノテーションのフィルタリング を参照してください。
The order_by argument behaves similarly to the field_names input of the
order_by() function, accepting a field name (with an optional
"-" prefix which indicates descending order) or an expression (or a tuple
or list of strings and/or expressions) that specifies the ordering of the
elements in the result.
引数 default は Coalesce に集計句と一緒に渡される値を取ります。これは、クエリセット(またはグループ化)にエントリがない場合に None 以外の値を返すように指定するのに便利です。
この **extra キーワード引数は template 属性に補間できる key=value のペアです。
The order_by argument was added.
独自の集計関数 (Aggregate Function) を作る¶
独自の集計関数を作成することもできます。最低限 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 の値を対応するデータベース型に変換する方法を知っています。
If no output_field is specified, it will be inferred from
the type of the provided value for many common types. For example, passing
an instance of datetime.datetime as value defaults
output_field to DateTimeField.
ExpressionWrapper() 式¶
ExpressionWrapper は他の式を囲み、 output_field のような他の式では利用できないプロパティへのアクセスを提供します。 ExpressionWrapper は F() をアノテーションと一緒に使う で説明されているように、異なる型を持つ F() 式で算術演算を行う場合に必要です。
Database casting not performed
ExpressionWrapper only sets the output field for the ORM and does not
perform any database-level casting. To ensure a specific type is returned
from the database, use Cast instead.
条件式¶
Conditional expressions allow you to use if ... elif ...
else logic in queries. Django natively supports SQL CASE
expressions. For more details see 条件式.
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行に絞る¶
サブクエリが複数行を返すのを防ぐには、クエリセットのスライス ([: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 がある場合、これを解決することはできません)。
素の SQL 式¶
データベース式では複雑な 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は名前付きウィンドウを使用しませんが、代わりに選択されたカラムの一部となります。
- class Window(expression, partition_by=None, order_by=None, frame=None, output_field=None)[ソース]¶
- template¶
デフォルトは
%(expression)s OVER (%(window)s)です。引数expressionだけを指定した場合、window 句は空白になります。
Window クラスは OVER 句のメインの式です。
引数の expression は ウィンドウ関数, 集計関数, またはwindow句で互換性のある式です。
The partition_by argument accepts an expression or a sequence of
expressions (column names should be wrapped in an F-object) that control
the partitioning of the rows. Partitioning narrows which rows are used to
compute the result set.
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() のオプションはサポートされていないかもしれません。必要に応じてデータベースのドキュメントを参照してください。
フレーム¶
ウィンドウフレームでは、範囲ベースの行のシーケンスか、通常の行のシーケンスのいずれかを選択できます。
- class ValueRange(start=None, end=None, exclusion=None)[ソース]¶
- frame_type¶
この属性は
'RANGE'に設定されています。
PostgreSQLは
ValueRangeのサポートに制限があり、CURRENT ROWやUNBOUNDED FOLLOWINGのような標準的な開始点と終了点の使用しかサポートしていません。
どちらのクラスもテンプレートとともに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),
... ),
... )
技術的な情報¶
以下に、ライブラリの作者に役立つ技術的な実装の詳細を示します。以下の技術的な API と例は、 Django が提供する組み込み機能を拡張する汎用的なクエリ式を作成するのに役立ちます。
式 (Expression) API¶
クエリ式は クエリ式 (expression) API を実装していますが、以下のようなメソッドや属性も公開しています。すべてのクエリ式は Expression() または関連するサブクラスを継承しなければなりません。
クエリ式が別の式をラップする場合、ラップされた式の適切なメソッドを呼び出す責任があります。
- class Expression[ソース]¶
- allowed_default¶
この式が
Field.db_defaultで使えることを Django に伝えます。デフォルトはFalseです。
- constraint_validation_compatible¶
この式が制約の検証中に使用できることを Django に教えます。
constraint_validation_compatibleがFalseに設定された式は、ソース式を1つだけ持つ必要があります。デフォルトではTrueです。
- contains_aggregate¶
Django に、この式が集計を含んでいて、クエリに
GROUP BY句を追加する必要があることを伝えます。
- contains_over_clause¶
この式が
Window式を含んでいることを Django に伝えます。これは例えば、データを変更するクエリでウィンドウ関数式を許可しないようにするのに使います。
- filterable¶
この式が
QuerySet.filter()で参照できることを Django に伝えます。デフォルトはTrueです。
- empty_result_set_value¶
Tells Django which value should be returned when the expression is used to apply a function over an empty result set. Defaults to
NotImplementedwhich forces the expression to be computed on the database.
- set_returning¶
- New in Django 5.2.
この式が集合を返す関数を含むことを Django に知らせ、サブクエリの評価を強制します。これは例えば、アノテーションが自ら行を生成する場合に、一部の Postgres の集合を返す関数(例:
JSONB_PATH_QUERY、UNNESTなど)が最適化をスキップして正しく評価されるようにするために使用されます。デフォルトはFalseです。
- allows_composite_expressions¶
- New in Django 5.2.
この式が複合式を許容することを Django に知らせます。例えば、 複合主キー をサポートするためなどに使用されます。デフォルトは
Falseです。
- resolve_expression(query=None, allow_joins=True, reuse=None, summarize=False, for_save=False)¶
クエリに追加する前に、式の前処理や検証を行う機会を提供します。
resolve_expression()は、ネストした式に対しても必ず呼び出されなければなりません。selfのcopy()は、必要な変換を行ったものを返すべきです。queryはバックエンドクエリの実装です。allow_joinsはクエリで結合を使用するかどうかを指定する真偽値です。reuseは複数結合シナリオのための再利用可能な結合のセットです。summarizeは真偽値で、Trueの場合、計算中のクエリが終端の集計クエリであることを示します。for_saveは真偽値で、Trueの場合、実行中のクエリが作成または更新を行うことを示します。
- get_source_expressions()¶
内部式の順序ありリストを返します。例えば:
>>> Sum(F("foo")).get_source_expressions() [F('foo')]
- set_source_expressions(expressions)¶
式のリストを受け取り、それを
get_source_expressions()が返せるように格納します。
- relabeled_clone(change_map)¶
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
- convert_value(value, expression, connection)¶
式が
valueをより適切な型に強制するためのフック。expressionはselfと同様です。
- get_group_by_cols()¶
この式によって参照されるカラムのリストを返す責任があります。
get_group_by_cols()は、任意のネストされた式に対して呼び出されるべきです。特に、F()オブジェクトはカラムへの参照を保持しています。
- asc(nulls_first=None, nulls_last=None)¶
昇順でソートする準備が整った式を返します。
nulls_firstとnulls_lastは、null値がどのようにソートされるかを定義します。使用例については、NULL 値のソートに F() を使う を参照してください。
- desc(nulls_first=None, nulls_last=None)¶
降順にソートする準備が整った式を返します。
nulls_firstとnulls_lastは、null値がどのようにソートされるかを定義します。使用例については、NULL 値のソートに F() を使う を参照してください。
- reverse_ordering()¶
Returns
selfwith any modifications required to reverse the sort order within anorder_bycall. As an example, an expression implementingNULLS LASTwould change its value to beNULLS FIRST. Modifications are only required for expressions that implement sort order likeOrderBy. This method is called whenreverse()is called on a queryset.
独自のクエリ式を書く¶
他のクエリ式を使用したり、他のクエリ式と統合したりする独自のクエリ式クラスを書くことができます。組み込みの Func() 式 を使用せずに COALESCE SQL関数の実装を書く例を見てみましょう。
SQL 関数 COALESCE はカラムや値のリストを受け取るように定義されています。この関数は NULL でない最初のカラムまたは値を返します。
まずはSQL生成に使用するテンプレートと、いくつかの属性を設定する __init__() メソッドを定義します:
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, tuple(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
SQL インジェクションを防ぐ¶
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() メソッドに記述します。