20

日付順に並べ替えられた、各顧客の最新の購入のリストを取得したいと考えています。

次のクエリは、日付を除いて私が望むことを行います:

(Purchase.objects
         .all()
         .distinct('customer')
         .order_by('customer', '-date'))

次のようなクエリが生成されます。

SELECT DISTINCT ON 
    "shop_purchase.customer_id" 
    "shop_purchase.id" 
    "shop_purchase.date" 
FROM "shop_purchase" 
ORDER BY "shop_purchase.customer_id" ASC, 
         "shop_purchase.date" DESC;

customer_idという理由で、最初のORDER BY式として使用せざるを得ませんDISTINCT ON

日付で並べ替えたいので、本当に必要なクエリは次のようになります。

SELECT * FROM (
  SELECT DISTINCT ON 
      "shop_purchase.customer_id" 
      "shop_purchase.id" 
      "shop_purchase.date" 
  FROM "shop_purchase" 
  ORDER BY "shop_purchase.customer_id" ASC, 
           "shop_purchase.date" DESC;
  )
AS result 
ORDER BY date DESC;

クエリをページ制限する必要があるため、Pythonを使用して並べ替えたくありません。データベースには何万行もある可能性があります。

実際、現在Pythonでソートされており、ページの読み込み時間が非常に長くなっているため、これを修正しようとしています.

基本的に、このhttps://stackoverflow.com/a/9796104/242969のようなものが欲しいです。生のSQLを書く代わりに、djangoクエリセットで表現することは可能ですか?

実際のモデルとメソッドは数ページの長さですが、上記のクエリセットに必要なモデルのセットは次のとおりです。

class Customer(models.Model):
  user = models.OneToOneField(User)

class Purchase(models.Model):
  customer = models.ForeignKey(Customer)
  date = models.DateField(auto_now_add=True)
  item = models.CharField(max_length=255)

次のようなデータがある場合:

Customer A - 
    Purchase(item=Chair, date=January), 
    Purchase(item=Table, date=February)
Customer B - 
    Purchase(item=Speakers, date=January), 
    Purchase(item=Monitor,  date=May)
Customer C - 
    Purchase(item=Laptop,  date=March), 
    Purchase(item=Printer, date=April)

以下を抽出できるようにしたい:

Purchase(item=Monitor, date=May)
Purchase(item=Printer, date=April)
Purchase(item=Table,   date=February)

リスト内の購入は、顧客ごとに最大 1 つです。購入は各顧客の最新のものです。最新の日付順にソートされます。

このクエリは、次のものを抽出できます。

SELECT * FROM (
  SELECT DISTINCT ON 
    "shop_purchase.customer_id" 
    "shop_purchase.id" 
    "shop_purchase.date" 
  FROM "shop_purchase" 
  ORDER BY "shop_purchase.customer_id" ASC, 
           "shop_purchase.date" DESC;
) 
AS result 
ORDER BY date DESC;

この結果を得るために生の SQL を使用する必要がない方法を見つけようとしています。

4

4 に答える 4

5

これはまさにあなたが探しているものではないかもしれませんが、あなたを近づけるかもしれません. Django の annotateを見てください。

役立つ可能性のあるものの例を次に示します。

  from django.db.models import Max
  Customer.objects.all().annotate(most_recent_purchase=Max('purchase__date'))

これにより、顧客モデルのリストが表示されます。各モデルには「most_recent_purchase」という新しい属性があり、最後に購入した日付が含まれます。生成される sql は次のようになります。

SELECT "demo_customer"."id", 
       "demo_customer"."user_id", 
       MAX("demo_purchase"."date") AS "most_recent_purchase"
FROM "demo_customer"
LEFT OUTER JOIN "demo_purchase" ON ("demo_customer"."id" = "demo_purchase"."customer_id")
GROUP BY "demo_customer"."id",
         "demo_customer"."user_id"

別のオプションは、次のようなプロパティを顧客モデルに追加することです。

  @property
  def latest_purchase(self):
    return self.purchase_set.order_by('-date')[0]

このプロパティで購入がまったくないケースを処理する必要があることは明らかであり、これはうまく機能しない可能性があります (顧客ごとに 1 つのクエリを実行して最新の購入を取得するため)。

私は過去にこれらの手法の両方を使用しましたが、どちらもさまざまな状況でうまく機能しました。これが役立つことを願っています。頑張ってください!

于 2013-01-16T00:39:37.800 に答える
4

Django ORM を使用して作成するのが難しいクエリがある場合は常に、最初に psql (または使用するクライアント) でクエリを試します。あなたが望むSQLはこれではありません

SELECT * FROM (
  SELECT DISTINCT ON 
    "shop_purchase.customer_id" "shop_purchase.id" "shop_purchase.date" 
  FROM "shop_purchase" 
  ORDER BY "shop_purchase.customer_id" ASC, "shop_purchase.date" DESC;
  ) AS result 
ORDER BY date DESC;

上記の SQL では、内部 SQL は (customer_id、id、および date) の組み合わせで個別のものを探しています。id はすべてに対して一意であるため、テーブルからすべてのレコードを取得します。慣例に従って、idが主キーであると想定しています。

すべての顧客の最後の購入を見つける必要がある場合は、次のようにする必要があります。

SELECT  "shop_purchase.customer_id", max("shop_purchase.date")
FROM shop_purchase
GROUP BY 1 

しかし、上記のクエリの問題は、顧客の名前と日付しか得られないことです。これらの結果をサブクエリで使用する場合、それを使用してもレコードを見つけるのに役立ちません。

使用するには、 idINなど、レコードを識別する一意のパラメーターのリストが必要です。

レコード内のidがシリアル キーである場合、最新の日付が最大 id になるという事実を利用できます。したがって、SQLは次のようになります。

SELECT  max("shop_purchase.id") 
FROM shop_purchase
GROUP BY "shop_purchase.customer_id";

IN を使用してサブクエリで使用するために、selected 句に1 つのフィールド ( id ) のみを保持したことに注意してください。

完全な SQL は次のようになります。

SELECT * 
FROM shop_customer 
WHERE "shop_customer.id" IN 
    (SELECT  max("shop_purchase.id") 
     FROM shop_purchase
     GROUP BY "shop_purchase.customer_id");

Django ORM を使用すると、次のようになります。

(Purchase.objects.filter(
    id__in=Purchase.objects
                   .values('customer_id')
                   .annotate(latest=Max('id'))
                   .values_list('latest', flat=True)))

それが役に立てば幸い!

于 2013-01-16T07:18:17.863 に答える
3

私は同様の状況にあり、これが私がそれについて行くことを計画している方法です:

query = Purchase.objects.distinct('customer').order_by('customer').query
query = 'SELECT * FROM ({}) AS result ORDER BY sent DESC'.format(query)
return Purchase.objects.raw(query)

逆に、必要なクエリが得られます。欠点は、生のクエリであり、他のクエリセット フィルターを追加できないことです。

于 2014-04-10T18:57:26.323 に答える
1

これは、Django クエリと共にデータのサブセット (N 個のアイテム) が必要な場合の私のアプローチです。これは PostgreSQL と便利なjson_build_object()関数 (Postgres 9.4+) を使用した例ですが、他のデータベース システムで他の集計関数を使用することもできます。array_agg()古いバージョンの PostgreSQL では、とarray_to_string()関数を組み合わせて使用​​できます。

モデルがArticleありComment、リスト内のすべての記事とともに、3 つの最近のコメントを選択したいとします (LIMIT 3サブセットのサイズを調整するか、ORDER BY c.id DESCサブセットの並べ替えを変更するために変更します)。

qs = Article.objects.all()
qs = qs.extra(select = {
    'recent_comments': """
    SELECT
        json_build_object('comments',
            array_agg(
              json_build_object('id', id, 'user_id', user_id, 'body', body)
            )
        )
    FROM (
        SELECT
          c.id,
          c.user_id,
          c.body
        FROM app_comment c
        WHERE c.article_id = app_article.id
        ORDER BY c.id DESC
        LIMIT 3
    ) sub
    """
})

for article in qs:
    print(article.recent_comments)

# Output:
# {u'comments': [{u'user_id': 1, u'id': 3, u'body': u'foo'}, {u'user_id': 1, u'id': 2, u'body': u'bar'}, {u'user_id': 1, u'id': 1, u'body': u'joe'}]}
# ....
于 2016-04-13T20:29:39.030 に答える