如何在django中使用子查询?

Eri*_*ric 19 django postgresql django-queryset

我想获得每个客户的最新购买清单,按日期排序.

以下查询执行我想要的日期除外:

(Purchase.objects
         .all()
         .distinct('customer')
         .order_by('customer', '-date'))
Run Code Online (Sandbox Code Playgroud)

它会产生如下查询:

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;
Run Code Online (Sandbox Code Playgroud)

我被迫使用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;
Run Code Online (Sandbox Code Playgroud)

我不想使用python排序,因为我仍然需要页面限制查询.数据库中可能有数万行.

实际上它现在在python中排序,并且导致非常长的页面加载时间,所以这就是我试图解决这个问题的原因.

基本上我想要这样的东西/sf/answers/685727311/.是否可以用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)
Run Code Online (Sandbox Code Playgroud)

如果我有以下数据:

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)
Run Code Online (Sandbox Code Playgroud)

我希望能够提取以下内容:

Purchase(item=Monitor, date=May)
Purchase(item=Printer, date=April)
Purchase(item=Table,   date=February)
Run Code Online (Sandbox Code Playgroud)

每个客户的列表中最多只有一次购买.购买是每个客户的最新消息.它按最新日期排序.

此查询将能够提取:

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;
Run Code Online (Sandbox Code Playgroud)

我正试图找到一种不必使用原始SQL来实现此结果的方法.

Mat*_*son 5

这可能不是您正在寻找的,但它可能会让您更接近.看看Django的注释.

以下是可能有所帮助的示例:

  from django.db.models import Max
  Customer.objects.all().annotate(most_recent_purchase=Max('purchase__date'))
Run Code Online (Sandbox Code Playgroud)

这将为您提供一份客户模型列表,其中每个客户模型都有一个名为"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"
Run Code Online (Sandbox Code Playgroud)

另一种选择是为您的客户模型添加一个属性,如下所示:

  @property
  def latest_purchase(self):
    return self.purchase_set.order_by('-date')[0]
Run Code Online (Sandbox Code Playgroud)

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

我过去曾使用过这两种技术,但它们在不同情况下都能很好地工作.我希望这有帮助.祝你好运!


Aja*_*dav 5

每当使用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;
Run Code Online (Sandbox Code Playgroud)

在上面的SQL中,内部SQL在(customer_id,id和date)的组合上寻找distinct,并且由于id对于所有人都是唯一的,因此您将从表中获取所有记录.我假设id是按照惯例的主键.

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

SELECT  "shop_purchase.customer_id", max("shop_purchase.date")
FROM shop_purchase
GROUP BY 1 
Run Code Online (Sandbox Code Playgroud)

但上述查询的问题在于它只会为您提供客户名称和日期.在子查询中使用这些结果时,使用它将无法帮助您查找记录.

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

如果您的记录ID是序列密钥,那么您可以利用最新日期也是最大ID的事实.所以你的SQL变成了:

SELECT  max("shop_purchase.id") 
FROM shop_purchase
GROUP BY "shop_purchase.customer_id";
Run Code Online (Sandbox Code Playgroud)

请注意,我在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");
Run Code Online (Sandbox Code Playgroud)

并使用Django ORM它看起来像:

(Purchase.objects.filter(
    id__in=Purchase.objects
                   .values('customer_id')
                   .annotate(latest=Max('id'))
                   .values_list('latest', flat=True)))
Run Code Online (Sandbox Code Playgroud)

希望能帮助到你!