繁体   English   中英

Django queryset:如何排除具有满足条件的任何相关对象的对象

[英]Django queryset : How to exclude objects with any related object satisfying a condition

我在进行困难的查询时偶然发现了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"))
))

可以“工作”,但感觉像是一种怪异的破解,对于某些简单的事情似乎效率不高。

你们中有人遇到过同样的问题并提出了更好的建议吗?

谢谢

编辑:感谢您的答案@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

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

仍然关于我的答案,我不明白的是为什么在一种情况下, AND U1."id" = ("stock"."id"))上有一个额外的过滤器AND U1."id" = ("stock"."id")) ,而在另一种情况下则不排除。 django是否有可能在queryset API中有怪异的行为?

您最好为此使用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)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM