簡體   English   中英

Django ORM 沒有為多對多不生成正確的 SQL

[英]Django ORM not generating correct SQL for many to many not in

我在使用 Django 從 ORM 生成的 SQL 時遇到問題。

Cartons通過cartons_shipmentsShipments多對多關系。

我希望排除至少有一個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 如果你有一個顯式的中間模型,這很容易,如果你沒有,你首先需要一個ShipmentCartons對象,因為據我所知你不能從類本身獲得對中間模型的引用,只能從一個實例.

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM