クエリ式 (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()
のように出力フィールドが必要になります。
例¶
>>> 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()
F()
は上記のように単一のインスタンスに対する操作で使用されるだけでなく、update()``と一緒にオブジェクトインスタンスの ``QuerySet
に対して使用することもできます。これにより、上記で使用していた2つのクエリである get()
と save()
を1つに減らすことができます:
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ではなく、データベースに行わせること
- 一部の操作に必要なクエリの数を減らすこと
Slicing F()
expressions¶
For string-based fields, text-based fields, and
ArrayField
, you can use Python's
array-slicing syntax. The indices are 0-based and the step
argument to
slice
is not supported. For example:
>>> # 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
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"
組み込みのデータベース関数の一覧は データベース関数 を参照してください。
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をカスタマイズするべきです。例えば: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, **extra)[ソース]¶ -
template
¶ この aggregate 用に生成される SQL を記述する、フォーマット文字列としてのクラス属性です。デフォルトは
'%(function)s(%(distinct)s%(expressions)s)'
です。
-
function
¶ 生成される集計関数を記述するクラス属性です。具体的には、
function
がtemplate
内のfunction
プレースホルダとして補間されます。デフォルトはNone
です。
-
allow_distinct
¶ この集計関数が
distinct
キーワード引数を渡すことができるかどうかを決定するクラス属性。False
(デフォルト) に設定されている場合、distinct=True
が渡されるとTypeError
が発生します。
-
empty_result_set_value
¶ ほとんどの集計関数は、空の結果セットに適用すると
NULL
となるため、デフォルトはNone
です。
-
位置引数 expressions
には、式、モデルフィールドのトランスフォーム、またはモデルフィールドの名前を指定できます。これらは文字列に変換され、 template
内の expressions
プレースホルダとして使用されます。
引数 distinct
は、集計関数を 式
(または複数の 式
の場合は値の集合) ごとに呼び出すかどうかを決定します。この引数は allow_distinct
が True
に設定されている集計でのみサポートされます。
引数 filter
は Q オブジェクト
を取り、集計する行をフィルタリングします。使用例については 条件付きの集計 と アノテーションのフィルタリング を参照してください。
引数 default
は Coalesce
に集計句と一緒に渡される値を取ります。これは、クエリセット(またはグループ化)にエントリがない場合に None
以外の値を返すように指定するのに便利です。
この **extra
キーワード引数は template
属性に補間できる key=value
のペアです。
独自の集計関数 (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
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行に絞る¶
サブクエリが複数行を返すのを防ぐには、クエリセットのスライス ([: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句で互換性のある式です。
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()
のオプションはサポートされていないかもしれません。必要に応じてデータベースのドキュメントを参照してください。
フレーム¶
ウィンドウフレームでは、範囲ベースの行のシーケンスか、通常の行のシーケンスのいずれかを選択できます。
-
class
ValueRange
(start=None, end=None, exclusion=None)[ソース]¶ -
frame_type
¶ この属性は
'RANGE'
に設定されています。
PostgreSQLは
ValueRange
のサポートに制限があり、CURRENT ROW
やUNBOUNDED FOLLOWING
のような標準的な開始点と終了点の使用しかサポートしていません。Changed in Django 5.1:The
exclusion
argument was added.-
-
class
RowRange
(start=None, end=None, exclusion=None)[ソース]¶ -
frame_type
¶ この属性は
'ROWS'
に設定されています。
Changed in Django 5.1:The
exclusion
argument was added.-
どちらのクラスもテンプレートとともにSQLを返します:
%(frame_type)s BETWEEN %(start)s AND %(end)s
The exclusion
argument allows excluding rows
(CURRENT_ROW
), groups
(GROUP
), and ties
(TIES
) from the window frames on supported
databases:
%(frame_type)s BETWEEN %(start)s AND %(end)s EXCLUDE %(exclusion)s
フレームは、結果を計算するために使用される行を絞り込みます。ある開始点から指定された終了点までシフトします。フレームはパーティションの有無に関係なく使用できますが、決定論的な結果を得るためには、ウィンドウの順序を指定することを推奨します。フレームでは、フレーム内のピアは等価値を持つ行であり、ソート句がない場合はすべての行です。
フレームのデフォルトの開始点は UNBOUNDED PRECEDING
で、パーティションの最初の行になります。終了点は ORM が生成する SQL に常に明示的に含まれ、デフォルトでは UNBOUNDED FOLLOWING
です。デフォルトのフレームは、パーティションからセットの最後の行までの全ての行を含みます。
The accepted values for the start
and end
arguments are None
, an
integer, or zero. A negative integer for start
results in N PRECEDING
,
while None
yields UNBOUNDED PRECEDING
. In ROWS
mode, a positive
integer can be used for start
resulting in N FOLLOWING
. Positive
integers are accepted for end
and results in N FOLLOWING
. In ROWS
mode, a negative integer can be used for end
resulting in N PRECEDING
.
For both start
and end
, zero will return 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),
... ),
... )
Support for positive integer start
and negative integer end
was
added for RowRange
.
技術的な情報¶
以下に、ライブラリの作者に役立つ技術的な実装の詳細を示します。以下の技術的な API と例は、 Django が提供する組み込み機能を拡張する汎用的なクエリ式を作成するのに役立ちます。
式 (Expression) API¶
クエリ式は クエリ式 (expression) API を実装していますが、以下のようなメソッドや属性も公開しています。すべてのクエリ式は Expression()
または関連するサブクラスを継承しなければなりません。
クエリ式が別の式をラップする場合、ラップされた式の適切なメソッドを呼び出す責任があります。
-
class
Expression
[ソース]¶ -
allowed_default
¶ - New in Django 5.0.
この式が
Field.db_default
で使えることを Django に伝えます。デフォルトはFalse
です。
-
constraint_validation_compatible
¶ - New in Django 5.1.
Tells Django that this expression can be used during a constraint validation. Expressions with
constraint_validation_compatible
set toFalse
must have only one source expression. Defaults toTrue
.
-
contains_aggregate
¶ Django に、この式が集計を含んでいて、クエリに
GROUP BY
句を追加する必要があることを伝えます。
-
contains_over_clause
¶ この式が
Window
式を含んでいることを Django に伝えます。これは例えば、データを変更するクエリでウィンドウ関数式を許可しないようにするのに使います。
-
filterable
¶ この式が
QuerySet.filter()
で参照できることを Django に伝えます。デフォルトはTrue
です。
-
empty_result_set_value
¶ Django に、式が空の結果セットに対して関数を適用する際にどの値を返すべきかを 指示します。デフォルトは
NotImplemented
で、式はデータベースで計算されます。
-
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() を使う を参照してください。
-
独自のクエリ式を書く¶
他のクエリ式を使用したり、他のクエリ式と統合したりする独自のクエリ式クラスを書くことができます。組み込みの 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
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()
メソッドに記述します。