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。是否可以用 django 查询集而不是编写原始 SQL 来表达它?

实际的模型和方法有几页长,但这里是上面查询集所需的模型集。

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)

每个客户的列表中最多有一次购买。购买是每个客户的最新产品。它按最新日期排序。

此查询将能够提取:

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]

您显然需要处理在此属性中没有任何购买的情况,这可能不会很好地执行(因为您将为每个客户运行一个查询以获取他们的最新购买)。

我过去使用过这两种技术,并且它们在不同的情况下都能正常工作。我希望这有帮助。祝你好运!

于 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) 的组合上查找 distinct,并且由于 id 对所有人都是唯一的,因此您将从表中获取所有记录。我假设 id 是按照约定的主键。

如果您需要找到每个客户的最后一次购买,您需要执行以下操作:

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

但上述查询的问题在于它只会给您客户姓名和日期。当您在子查询中使用这些结果时,使用它不会帮助您找到记录。

要使用IN,您需要一个唯一参数列表来识别记录,例如id

如果在您的记录中id是一个序列号,那么您可以利用最新日期也将是最大 id 的事实。所以你的 SQL 变成:

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

请注意,我在 selected 子句中只保留了一个字段 ( id ),以便在使用 IN 的子查询中使用它。

完整的 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

如果我需要一些数据子集(N 项)以及 Django 查询,这是我的方法。这是使用 PostgreSQL 和方便json_build_object()函数(Postgres 9.4+)的示例,但您可以在其他数据库系统中使用其他聚合函数。对于较旧的 PostgreSQL 版本,您可以使用array_agg()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 回答