[〜#〜] update [〜#〜]
投稿された回答のおかげで、問題を定式化するはるかに簡単な方法を見つけました。元の質問は改訂履歴で見ることができます。
SQLクエリをDjangoに変換しようとしていますが、理解できないエラーが発生しています。
Django私が持っているモデル:
class Title(models.Model):
title_id = models.CharField(primary_key=True, max_length=12)
title = models.CharField(max_length=80)
publisher = models.CharField(max_length=100)
price = models.DecimalField(decimal_places=2, blank=True, null=True)
次のデータがあります。
publisher title_id price title
--------------------------- ---------- ------- -----------------------------------
New Age Books PS2106 7 Life Without Fear
New Age Books PS2091 10.95 Is Anger the Enemy?
New Age Books BU2075 2.99 You Can Combat Computer Stress!
New Age Books TC7777 14.99 Sushi, Anyone?
Binnet & Hardley MC3021 2.99 The Gourmet Microwave
Binnet & Hardley MC2222 19.99 Silicon Valley Gastronomic Treats
Algodata Infosystems PC1035 22.95 But Is It User Friendly?
Algodata Infosystems BU1032 19.99 The Busy Executive's Database Guide
Algodata Infosystems PC8888 20 Secrets of Silicon Valley
私がやりたいことは次のとおりです。価格の2倍の注釈付きフィールドdbl_price
を導入し、結果のクエリセットをpublisher
でグループ化し、各出版社について、その出版社のすべてのタイトルのすべてのdbl_price
値の合計を計算します出版社。
これを行うSQLクエリは次のとおりです。
SELECT SUM(dbl_price) AS total_dbl_price, publisher
FROM (
SELECT price * 2 AS dbl_price, publisher
FROM title
) AS A
GROUP BY publisher
望ましい出力は次のとおりです。
publisher tot_dbl_prices
--------------------------- --------------
Algodata Infosystems 125.88
Binnet & Hardley 45.96
New Age Books 71.86
クエリは次のようになります。
Title.objects
.annotate(dbl_price=2*F('price'))
.values('publisher')
.annotate(tot_dbl_prices=Sum('dbl_price'))
エラーが発生します:
KeyError: 'dbl_price'.
これは、クエリセットでフィールドdbl_price
が見つからないことを示します。
このエラーが発生する理由は次のとおりです。 ドキュメントには
また、返される値のリストにaverage_ratingが明示的に含まれていることにも注意してください。これは、values()およびannotate()句の順序のために必要です。
Values()句がannotate()句の前にある場合、注釈は結果セットに自動的に追加されます。ただし、values()句がannotate()句の後に適用される場合、集計列を明示的に含める必要があります。
したがって、dbl_price
は、以前のannotate
によって作成されたが、values()
には含まれていなかったため、集約で見つかりませんでした。
ただし、values
(その後に別のvalues
が続く)をグループ化デバイスとして使用するため、annotate
にも含めることはできません。
Values()句がannotate()の前にある場合、values()句で記述されたグループ化を使用して注釈が計算されます。
これはDjango がSQL GROUP BY
を実装する方法の基礎です。これは、values()
の中にdbl_price
を含めることができないことを意味します。なぜなら、グループ化はpublisher
とdbl_price
の両方のフィールドの一意の組み合わせに基づいているのに対し、publisher
のみでグループ化する必要があるからです。
したがって、次のクエリは、注釈付きのdbl_price
フィールドではなく、モデルのprice
フィールドを集計するという点でのみ上記と異なり、実際に機能します。
Title.objects
.annotate(dbl_price=2*F('price'))
.values('publisher')
.annotate(sum_of_prices=Count('price'))
price
フィールドは注釈付きフィールドではなくモデル内にあるため、クエリセットに保持するためにvalues
に含める必要はありません。
したがって、ここにあります:注釈セットのプロパティをクエリセットに保持するためにvalues
に含める必要がありますが、values
はグループ化にも使用されるため、できません(これは余分なフィールドが間違っています)。問題は本質的に、コンテキストに応じて、values
がDjangoで使用される2つの非常に異なる方法によるものです(values
の後にannotate
が続くかどうか)- (1)値抽出(SQLプレーンSELECT
リスト)および(2)グループ化+グループの集約(SQL GROUP BY
)-この場合、これら2つの方法は競合するようです。
私の質問は:この問題を解決する方法はありますか(生のSQLにフォールバックするようなことなしに)?
注意:問題の特定の例は、いくつかの回答で指摘されたannotate
の後にすべてのvalues
ステートメントを移動することで解決できます。ただし、3つの理由から、values()
の前にannotate
ステートメントを保持するソリューション(または議論)に興味があります。1。より複雑な例もあります。推奨される回避策は機能しません。 2.注釈付きクエリセットが別の関数に渡され、実際にGROUP BYが実行される状況を想像できます。そのため、注釈付きフィールドの名前とその型のセットしかわかりません。 3.状況は非常に単純であるように思われ、values()
の2つの異なる使用法のこの衝突が以前に気づかれず、議論されていなかった場合、私は驚きます。
これは少し遅すぎるかもしれませんが、解決策を見つけました(Django 1.11.1でテスト済み))。
問題は、グループ化を提供するために必要な.values('publisher')
を呼び出すと、.values()
fields paramに含まれないすべての注釈が削除されることです。
また、_dbl_price
_をfields paramに含めることはできません。別の_GROUP BY
_ステートメントが追加されるためです。
注釈付きフィールドを最初に必要とするすべての集計を作成するソリューションは、.values()
を呼び出し、その集計をfields paramに含めます(これは_GROUP BY
_を追加しませんそれらは集約です)。次に、任意の式で.annotate()
を呼び出す必要があります。これにより、クエリ内の非集計フィールドのみを使用して、SQLクエリに_GROUP BY
_ステートメントを追加するDjango パブリッシャー。
_Title.objects
.annotate(dbl_price=2*F('price'))
.annotate(sum_of_prices=Sum('dbl_price'))
.values('publisher', 'sum_of_prices')
.annotate(titles_count=Count('id'))
_
このアプローチの唯一のマイナス点-注釈付きフィールドを使用するもの以外の他の集計が必要ない場合は、とにかくいくつかを含める必要があります。 .annotate()への最後の呼び出しがなければ(そして、少なくとも1つの式を含める必要があります!)、DjangoはSQLクエリに_GROUP BY
_を追加しません。これに対処する1つのアプローチはフィールドのコピーを作成するには:
_Title.objects
.annotate(dbl_price=2*F('price'))
.annotate(_sum_of_prices=Sum('dbl_price')) # note the underscore!
.values('publisher', '_sum_of_prices')
.annotate(sum_of_prices=F('_sum_of_prices')
_
また、QuerySetの順序に注意する必要があることにも言及してください。 .order_by()
を呼び出して、パラメーターを指定せずに順序をクリアするか、_GROUP BY
_フィールドを使用することをお勧めします。結果のクエリに他のフィールドによる順序付けが含まれる場合、グループ化は間違っています。 https://docs.djangoproject.com/en/1.11/topics/db/aggregation/#interaction-with-default-ordering-or-order-by
また、その偽の注釈を出力から削除したい場合がありますので、再度.values()を呼び出してください。したがって、最終的なコードは次のようになります。
_Title.objects
.annotate(dbl_price=2*F('price'))
.annotate(_sum_of_prices=Sum('dbl_price'))
.values('publisher', '_sum_of_prices')
.annotate(sum_of_prices=F('_sum_of_prices')
.values('publisher', 'sum_of_prices')
.order_by('publisher')
_
これは、Djangoの group_byの動作 の方法から予想されます。すべての注釈付きフィールドは、GROUP BY
句に追加されます。ただし、このように書かれた理由についてはコメントできません。
クエリを次のように動作させることができます。
Title.objects
.values('publisher')
.annotate(total_dbl_price=Sum(2*F('price'))
次のSQLを生成します。
SELECT publisher, SUM((2 * price)) AS total_dbl_price
FROM title
GROUP BY publisher
ちょうどあなたのケースで動作します。
私はこれがあなたが探していた完全な解決策ではないかもしれないと理解していますが、 CombinedExpressions (私は願っています!).
問題はvalues()
に続き、annotate()
が原因です。順序が重要です。これは、[注釈と値の句の順序]( https://docs.djangoproject.com/en/1.10/topics/db/aggregation/#order-of-annotate-and-values-節 )
.values('pub_id')
pub_id
でクエリセットフィールドを制限します。したがって、income
に注釈を付けることはできません
Values()メソッドは、オプションの位置引数* fieldsを取ります。これは、SELECTを制限するフィールド名を指定します。
@alexandrによるこのソリューションは、適切に対処します。
https://stackoverflow.com/a/44915227/6323666
必要なものはこれです:
from Django.db.models import Sum
Title.objects.values('publisher').annotate(tot_dbl_prices=2*Sum('price'))
理想的には、最初にそれらを合計してから2倍にして、ここでシナリオを逆にしました。あなたはそれを2倍にして、合計しようとしました。これでいいことを願っています。