首页 > 解决方案 > Django queryset:如何排除具有满足条件的任何相关对象的对象

问题描述

我在进行困难查询时偶然发现了 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"))而在另一种情况下则没有。django 有可能在查询集 API 中有一个奇怪的行为吗?

标签: pythondjangopostgresql

解决方案


你最好使用 a 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)

推荐阅读