[英]Django ORM not generating correct SQL for many to many not in
我在使用 Django 從 ORM 生成的 SQL 時遇到問題。
Cartons
通過cartons_shipments
與Shipments
多對多關系。
我希望排除至少有一個INBOUND
Carton 狀態為['TRANSIT', 'DELIVERED', 'FAILURE']
。
但是我沒有得到預期的結果,所以我打開了 SQL 日志記錄。
return Shipment.objects.filter(
... # other filtering
# does not have any inbound cartons in_transit/delivered/failed
~Q(
Q(cartons__type='INBOUND') &
Q(cartons__status__in=['TRANSIT', 'DELIVERED', 'FAILURE'])
) &
).distinct()
我也試過這個作為我的過濾器,但得到了相同的 SQL 輸出。
~Q(
cartons__type='INBOUND',
cartons__status__in=['TRANSIT', 'DELIVERED', 'FAILURE']
)
這將生成此 SQL:
AND NOT (
"shipments"."id" IN (
SELECT U1."shipment_id"
FROM "cartons_shipments" U1
INNER JOIN "cartons" U2 ON (U1."carton_id" = U2."id")
WHERE U2."type" = 'INBOUND'
)
AND "shipments"."id" IN (
SELECT U1."shipment_id" FROM "cartons_shipments" U1
INNER JOIN "cartons" U2 ON (U1."carton_id" = U2."id")
WHERE U2."status" IN ('TRANSIT', 'DELIVERED', 'FAILURE')
)
)
但這不正確,因為它會排除有任何INBOUND
紙箱的貨件和任何紙箱(不一定是INBOUND
紙箱)狀態為['TRANSIT', 'DELIVERED', 'FAILURE']
貨件。 我需要結合這個邏輯。
此外,現在我正在運行 2 個子選擇,並且性能受到顯着影響,因為我們有大量處於這些狀態的紙箱。
正確的 SQL 應該是這樣的:
AND NOT ("shipments"."id" IN (
SELECT U1."shipment_id"
FROM "cartons_shipments" U1
INNER JOIN "cartons" U2 ON (U1."carton_id" = U2."id")
WHERE U2."type" = 'INBOUND'
and U2."status" IN ('TRANSIT', 'DELIVERED', 'FAILURE')
))
通過這種方式,我只會排除這些狀態下帶有INBOUND
紙箱的貨件。
這兩者之間的查詢時間很重要,當然我能夠通過第二個 SQL 示例獲得正確的結果。 我認為我可以通過組合Q()
對象來組合該邏輯。 但是想不通。
我還認為也許我可以在第二個示例中正確使用原始 SQL。 但是我很難弄清楚如何將原始 sql 與其他 ORM 過濾器結合起來。
任何幫助將不勝感激。
編輯:
通過在代碼中進行過濾並從查詢中刪除過濾器,我能夠獲得正確的結果:
returned_cartons = Carton.objects.prefetch_related('shipments').filter(
type='INBOUND',
status__in=['TRANSIT', 'DELIVERED', 'FAILURE']
)
returned_shipment_ids = list(map(
lambda carton: carton.shipments.first().id,
returned_cartons
))
return list(filter(
lambda shipment: shipment.id not in returned_shipment_ids,
shipments
))
不幸的是,這太慢而無用。
基於 Endre Both 想法的最終解決方案🙌
return Shipment.objects.filter(
..., # other filtering
# has at least 1 inbound carton
Q(cartons__type='INBOUND')
).exclude(
# we want to exclude shipments that have at least 1 inbound cartons
# with a status in transit/delivered/failure
id__in=Shipment.objects.filter(
..., # filters to limit the number of records returned
cartons__type='INBOUND',
cartons__status__in=['TRANSIT', 'DELIVERED', 'FAILURE'],
).distinct()
).distinct()
這行Q(cartons__type='INBOUND')
是必需的,因為我們排除了具有['TRANSIT', 'DELIVERED', 'FAILURE']
狀態的INBOUND
Carton 的貨件。 但我們也會保留沒有任何紙箱的貨件。
希望這能幫助更多的人。
對於我們凡人來說,ORM 中的“M”有時可能有點難以理解。 但是你可以嘗試一種不同的、更簡單的方法。 它仍然使用子查詢而不是連接,但這不一定會拖累性能。
Shipment.objects.exclude(
id__in=Cartons.objects
.filter(type='INBOUND',
status__in=['TRANSIT', 'DELIVERED', 'FAILURE'])
.values('shipments__id')
.distinct()
)
從Carton
模型返回到Shipment
主鍵的引用的確切名稱取決於模型的確切定義。 我已經使用過shipments__id
,但它可能是shipment_set__id
或其他東西。
新想法:您需要將子選擇基於中間模型而不是Cartons
。 如果你有一個顯式的中間模型,這很容易,如果你沒有,你首先需要一個Shipment
或Cartons
對象,因為據我所知你不能從類本身獲得對中間模型的引用,只能從一個實例.
IModel = Shipment.objects.first().cartons.through
Shipment.objects.exclude(
id__in=IModel.objects
.filter(cartons__type='INBOUND',
cartons__status__in=['TRANSIT', 'DELIVERED', 'FAILURE'])
.values('shipment__id')
.distinct()
)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.