【问题标题】:Django queryset : How to exclude objects with any related object satisfying a conditionDjango queryset:如何排除具有满足条件的任何相关对象的对象
【发布时间】:2019-09-17 09:07:01
【问题描述】:

我在进行困难查询时偶然发现了 django 查询集的一种奇怪行为,我想知道是否有人知道如何改进此查询。

基本上我有这样的模型:

class Product(models.Model):
    pass

class Stock(models.Model):
    product_id = models.ForeignKey(Product)
    date = models.DateField()
    initial_stock = models.SmallIntegerField()
    num_ordered = models.SmallIntegerField()

我想选择在任何日期都不可用的所有产品(意味着没有与产品相关的stock 对象,其initial_stock 字段大于num_ordered 字段)。所以一开始,我是这样做的:

Product.objects.exclude(stock__initial_stock__gt=F('stock__num_ordered')).distinct()

但我查了一下,这个查询翻译为:

SELECT DISTINCT *
FROM "product"
LEFT OUTER JOIN "stock"
ON ("product"."id" = "stock"."product_id")
WHERE NOT ("product"."id" IN (
    SELECT U1."product_id" AS Col1 
    FROM "product" U0 
    INNER JOIN "stock" U1 
    ON (U0."id" = U1."product_id") 
    WHERE (U1."initial_stock" > (U1."num_ordered") AND U1."id" = ("stock"."id"))
))

这会对股票进行左连接,然后过滤掉 initial_stock 大于 num_ordered 的行,然后再将不同的行发回给我。

如您所见,当我有一个缺货的库存对象和另一个没有缺货的库存对象时,它不起作用。

过滤后,我被排除在外的还有其他日期实际可用的产品。

经过多次尝试,我认为这是可行的:

Product.objects.exclude(
    stock__initial_stock__gt=F('stock__num_ordered')
).exclude(
    stock__initial_stock__gt=F('stock__num_ordered')
).distinct()

因为它翻译为:

SELECT *
FROM "product"
LEFT OUTER JOIN "stock"
ON ("product"."id" = "stock"."product_id")
LEFT OUTER JOIN "stock" T3
ON ("product"."id" = T3."product_id")
WHERE NOT ("product"."id" IN (
    SELECT U1."product_id" AS Col1 
    FROM "product" U0 
    INNER JOIN "stock" U1 
    ON (U0."id" = U1."product_id") 
    WHERE (U1."initial_stock" > (U1."num_ordered") AND U1."id" = ("stock"."id"))    )
)) AND NOT ("product"."id" IN (
    SELECT U1."product_id" AS Col1 
    FROM "product" U0 
    INNER JOIN "stock" U1 
    ON (U0."id" = U1."product_id") 
    WHERE U1."initial_stock" > (U1."num_ordered"))
))

“有效”,但感觉像是一种奇怪的 hack,对于这么简单的事情似乎效率不高。

你们中有人遇到过同样的问题并想出了更好的办法吗?

谢谢

编辑: 感谢@dirkgroten的回答,为了比较,让我写下sql查询结果:

SELECT *,
       EXISTS(
        SELECT *
          FROM "stock" U0
         WHERE (U0."product_id" = ("product"."id") AND U0."initial_stock" > (U0."num_ordered"))
       ) AS "has_stock"
  FROM "product"
 WHERE EXISTS(
        SELECT *
          FROM "stock" U0
         WHERE (U0."product_id" = ("product"."id") AND U0."initial_stock" > (U0."num_ordered"))
       ) = false

这 2 个查询似乎具有相同的执行时间,即使您的查询看起来更好。 虽然我很困惑为什么您的注释的以下过滤器不使用注释创建的列而不是在 WHERE... 中再次进行查询...

关于我的回答,我不明白为什么在一种情况下AND U1."id" = ("stock"."id")) 上有一个额外的过滤器,而在另一种情况下则没有。 django 有可能在查询集 API 中有一个奇怪的行为吗?

【问题讨论】:

    标签: python django postgresql


    【解决方案1】:

    你最好使用 Subquery 来解决这个问题:

    from django.db.models import OuterRef, Exists
    
    in_stock = Stock.objects.filter(
        product_id=OuterRef('pk'), 
        initial_stock__gt=F('num_ordered'))
    
    out_of_stock_products = Product.objects.annotate(has_stock=Exists(in_stock))\
                                           .filter(has_stock=False)
    

    【讨论】:

      猜你喜欢
      • 2017-07-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-30
      • 1970-01-01
      相关资源
      最近更新 更多