[英]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.