これは最先端の機能であり、現在私は偏っていて、すぐに出血します。既存のクエリセットにサブクエリ集計に注釈を付けたい。 1.11より前にこれを行うと、カスタムSQLを実行するか、データベースをハンマーで操作することになります。 これに関するドキュメントはこちらです 、およびその例:
from Django.db.models import OuterRef, Subquery, Sum
comments = Comment.objects.filter(post=OuterRef('pk')).values('post')
total_comments = comments.annotate(total=Sum('length')).values('total')
Post.objects.filter(length__gt=Subquery(total_comments))
それらは集合体に注釈を付けています。
私はこれに苦労しているので、データを持っている最も単純な実世界の例に戻って説明します。 Carpark
sを多く含むSpace
sがあります。つかいます Book→Author
それがあなたをより幸せにするが、今のところは、Subquery
*を使用して関連するモデルの数に注釈を付けたいだけです。
spaces = Space.objects.filter(carpark=OuterRef('pk')).values('carpark')
count_spaces = spaces.annotate(c=Count('*')).values('c')
Carpark.objects.annotate(space_count=Subquery(count_spaces))
これにより、素敵なProgrammingError: more than one row returned by a subquery used as an expression
そして私の頭の中では、このエラーは完全に理にかなっています。サブクエリは、注釈付きの合計を含むスペースのリストを返します。
この例は、ある種の魔法が発生することを示唆しており、使用できる数字になります。しかし、それはここでは起きていませんか?集計サブクエリデータに注釈を付けるにはどうすればよいですか?
新しいCarpark/Spaceモデルを作成し、それが機能しました。したがって、次のステップは、SQLを汚染しているものを特定することです。ローランのアドバイスで、私はSQLを見て、彼らが答えに投稿したバージョンのようにしようと試みました。そして、これは私が本当の問題を見つけた場所です:
SELECT "bookings_carpark".*, (SELECT COUNT(U0."id") AS "c"
FROM "bookings_space" U0
WHERE U0."carpark_id" = ("bookings_carpark"."id")
GROUP BY U0."carpark_id", U0."space"
)
AS "space_count" FROM "bookings_carpark";
ハイライトしましたが、サブクエリのGROUP BY ... U0."space"
。何らかの理由で両方を再調整しています。調査は続けられます。
編集2:さて、サブクエリSQLを見るだけで、2番目のグループを見ることができます☹
In [12]: print(Space.objects_standard.filter().values('carpark').annotate(c=Count('*')).values('c').query)
SELECT COUNT(*) AS "c" FROM "bookings_space" GROUP BY "bookings_space"."carpark_id", "bookings_space"."space" ORDER BY "bookings_space"."carpark_id" ASC, "bookings_space"."space" ASC
Edit 3:OK!これらのモデルには両方ともソート順があります。これらはサブクエリに引き継がれています。これらの注文が私のクエリを膨らませ、破壊しているのです。
これはDjangoのバグかもしれませんが、これらの両方のモデルでMeta-order_byを削除するのではなく、私ができる方法はありますかunsortクエリ時のクエリ?
*この例では、カウントに注釈を付けることができます。これを使用する私の本当の目的は、はるかに複雑なフィルターカウントですが、これを動作させることさえできません。
Subquery
のサブクラスを作成して、出力するSQLを変更することもできます。たとえば、次を使用できます。
class SQCount(Subquery):
template = "(SELECT count(*) FROM (%(subquery)s) _count)"
output_field = models.IntegerField()
次に、元のSubquery
クラスと同じようにこれを使用します。
spaces = Space.objects.filter(carpark=OuterRef('pk')).values('pk')
Carpark.objects.annotate(space_count=SQCount(spaces))
このトリックを(少なくともpostgresでは)さまざまな集約関数で使用できます。値の配列を構築したり、それらを合計したりするためによく使用します。
シャザーム!編集ごとに、追加の列がサブクエリから出力されていました。これは、注文を容易にするためでした(COUNTでは必要ありません)。
モデルから所定のメタオーダーを削除する必要がありました。これを行うには、サブクエリに空の.order_by()
を追加するだけです。私のコード用語では:
spaces = Space.objects.filter(carpark=OuterRef('pk')).order_by().values('carpark')
count_spaces = spaces.annotate(c=Count('*')).values('c')
Carpark.objects.annotate(space_count=Subquery(count_spaces))
そしてそれは動作します。素晴らしい。とても迷惑。
私は非常によく似たケースにぶつかりました。そこでは、予約ステータスがキャンセルされないイベントの座席予約を取得する必要がありました。何時間も問題を解明しようとした後、私は問題の根本原因として見たものをここにあります:
序文:これはMariaDB、Django 1.11。
クエリに注釈を付けると、選択したフィールドを含む_GROUP BY
_句を取得します(基本的に、values()
クエリ選択の内容)。クエリ結果でNULL
sまたはNone
sを取得している理由をMariaDBコマンドラインツールで調査したところ、_GROUP BY
_句がCOUNT()
を引き起こすという結論に達しました。 NULL
sを返します。
それから、QuerySet
インターフェースに飛び込み、DBクエリから_GROUP BY
_を手動で強制的に削除する方法を確認し、次のコードを思い付きました。
_from Django.db.models.fields import PositiveIntegerField
reserved_seats_qs = SeatReservation.objects.filter(
performance=OuterRef(name='pk'), status__in=TAKEN_TYPES
).values('id').annotate(
count=Count('id')).values('count')
# Query workaround: remove GROUP BY from subquery. Test this
# vigorously!
reserved_seats_qs.query.group_by = []
performances_qs = Performance.objects.annotate(
reserved_seats=Subquery(
queryset=reserved_seats_qs,
output_field=PositiveIntegerField()))
print(performances_qs[0].reserved_seats)
_
したがって、基本的に、実行時に_group_by
_が付加されないようにするには、サブクエリのクエリセットの_GROUP BY
_フィールドを手動で削除/更新する必要があります。また、Djangoは自動的に認識できず、クエリセットの最初の評価で例外を発生させるため、サブクエリの出力フィールドを指定する必要があります。 2番目の評価はそれなしで成功します。
これはDjangoバグ、またはサブクエリの非効率性だと思います。それについてのバグレポートを作成します。
編集: バグレポートはこちら 。
私が正しく理解していれば、Space
で利用可能なCarpark
sをカウントしようとしています。これに対してサブクエリはやり過ぎだと思われます。古き良き注釈aloneがトリックを行うはずです:
Carpark.objects.annotate(Count('spaces'))
これには、結果にspaces__count
値が含まれます。
OK、メモを見ました...
また、手元にある他のモデルでも同じクエリを実行できました。結果は同じなので、例のクエリは問題ないようです(Django 1.11b1でテスト済み):
activities = Activity.objects.filter(event=OuterRef('pk')).values('event')
count_activities = activities.annotate(c=Count('*')).values('c')
Event.objects.annotate(spaces__count=Subquery(count_activities))
あなたの「最も単純な実世界の例」は単純すぎるかもしれません...モデルや他の情報を共有できますか?
一般的な集計で機能するソリューションは、Django 2.0のWindow
クラスを使用して実装できます。これをDjangoトラッカーに追加しましたチケットも。
これにより、(GROUP BY句内の)外部クエリモデルに基づいてパーティションの集計を計算し、そのデータにサブクエリクエリセットのすべての行に注釈を付けることで、注釈付きの値の集計が可能になります。サブクエリは、返された最初の行の集計データを使用し、他の行を無視できます。
Performance.objects.annotate(
reserved_seats=Subquery(
SeatReservation.objects.filter(
performance=OuterRef(name='pk'),
status__in=TAKEN_TYPES,
).annotate(
reserved_seat_count=Window(
expression=Count('pk'),
partition_by=[F('performance')]
),
).values('reserved_seat_count')[:1],
output_field=FloatField()
)
)
「私のために働く」はあまり役に立ちません。しかし。便利なモデル(_Book -> Author
_タイプ)で例を試してみましたが、Django 1.11b1でうまく動作します。
これを正しいバージョンのDjangoで実行していると確信していますか?これはあなたが実行している実際のコードですか?実際にcarpark
ではなく、より複雑なモデルでこれをテストしていますか?
print(thequery.query)
を試して、データベースで実行しようとしているSQLを確認してください。以下は私のモデルで得たものです(あなたの質問に合うように編集されています):
_SELECT (SELECT COUNT(U0."id") AS "c"
FROM "carparks_spaces" U0
WHERE U0."carpark_id" = ("carparks_carpark"."id")
GROUP BY U0."carpark_id") AS "space_count" FROM "carparks_carpark"
_
本当の答えではありませんが、うまくいけば助けになります。