У меня проблема с сгенерированным Django SQL из ORM.
Cartons
имеют отношения многие ко многим с Shipments
через cartons_shipments
.
Япытаясь исключить отгрузки, у которых есть хотя бы одна INBOUND
коробка, которая имеет статус ['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 во 2-м примере.Но мне трудно разобраться, как объединить raw 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')
обязательна, поскольку мы исключаем Отправления, имеющие INBOUND
Картон в статусах ['TRANSIT', 'DELIVERED', 'FAILURE']
.Но мы также сохранили бы отгрузки, в которых нет коробок.
Надеюсь, это поможет большему количеству людей.