Я пытаюсь использовать Django ORM для создания набора запросов и не могу найти, как использовать OuterRef
в условии соединения с FilteredRelation
.
Что у меня есть в Джанго
Основной набор запросов
queryset = LineOutlier.objects.filter(report=self.kwargs['report_pk'], report__apn__customer__cen_id=self.kwargs['customer_cen_id']) \
.select_related('category__traffic') \
.select_related('category__frequency') \
.select_related('category__stability') \
.prefetch_related('category__traffic__labels') \
.prefetch_related('category__frequency__labels') \
.prefetch_related('category__stability__labels') \
.annotate(history=subquery)
Подзапрос
subquery = ArraySubquery(
LineOutlierReport.objects
.filter((Q(lineoutlier__imsi=OuterRef('imsi')) | Q(lineoutlier__isnull=True)) & Q(id__in=last_5_reports_ids))
.values(json=JSONObject(
severity='lineoutlier__severity',
report_id='id',
report_start_date='start_date',
report_end_date='end_date'
)
)
)
Запрос может быть выполнен, но сгенерированный SQL не совсем то, что я хочу:
Сгенерированный SQL
SELECT "mlformalima_lineoutlier"."id",
"mlformalima_lineoutlier"."imsi",
ARRAY(
SELECT JSONB_BUILD_OBJECT('severity', V1."severity", 'report_id', V0."id", 'report_start_date', V0."start_date", 'report_end_date', V0."end_date") AS "json"
FROM "mlformalima_lineoutlierreport" V0
LEFT OUTER JOIN "mlformalima_lineoutlier" V1
ON (V0."id" = V1."report_id")
WHERE ((V1."imsi" = ("mlformalima_lineoutlier"."imsi") OR V1."id" IS NULL) AND V0."id" IN (SELECT DISTINCT ON (U0."id") U0."id" FROM "mlformalima_lineoutlierreport" U0 WHERE U0."apn_id" = 2 ORDER BY U0."id" ASC, U0."end_date" DESC LIMIT 5))
) AS "history",
FROM "mlformalima_lineoutlier"
Проблема здесь в том, что OuterRef
условие (V1."imsi" = ("mlformalima_lineoutlier"."imsi"))
выполняется в операторе WHERE
, а я хочу, чтобы оно было в операторе JOIN
.
Что я хочу в SQL
SELECT "mlformalima_lineoutlier"."id",
"mlformalima_lineoutlier"."imsi",
ARRAY(
SELECT JSONB_BUILD_OBJECT('severity', V1."severity", 'report_id', V0."id", 'report_start_date', V0."start_date", 'report_end_date', V0."end_date") AS "json"
FROM "mlformalima_lineoutlierreport" V0
LEFT OUTER JOIN "mlformalima_lineoutlier" V1
ON (V0."id" = V1."report_id" AND ((V1."id" IS NULL) OR V1."imsi" = ("mlformalima_lineoutlier"."imsi")))
WHERE V0."id" IN (SELECT DISTINCT ON (U0."id") U0."id" FROM "mlformalima_lineoutlierreport" U0 WHERE U0."apn_id" = 2 ORDER BY U0."id" ASC, U0."end_date" DESC LIMIT 5))
) AS "history",
FROM "mlformalima_lineoutlier"
Что я пробовал в Джанго
Я пытался использовать FilteredRelation
для изменения условия JOIN, но не могу использовать его в сочетании с OuterRef
subquery = ArraySubquery(
LineOutlierReport.objects
.annotate(filtered_relation=FilteredRelation('lineoutlier', condition=Q(lineoutlier__imsi=OuterRef('imsi')) | Q(lineoutlier__isnull=True)))
.filter(Q(id__in=last_5_reports_ids))
.values(json=JSONObject(
severity='filtered_relation__severity',
report_id='id',
report_start_date='start_date',
report_end_date='end_date'
)
)
)
Я не могу выполнить этот запрос из-за следующей ошибки
ValueError: This queryset contains a reference to an outer query and may only be used in a subquery.
Как я могу изменить свой запрос, чтобы он работал?
Это похоже на баг Django. В качестве обходного пути вы можете аннотировать другой столбец и сослаться на него в FilteredRelation
, например:
subquery = ArraySubquery(
LineOutlierReport.objects
.annotate(
outer_imsi=OuterRef('imsi'),
filtered_relation=FilteredRelation('lineoutlier', condition=Q(lineoutlier__imsi=F('outer_imsi')) | Q(lineoutlier__isnull=True)))
.filter(Q(id__in=last_5_reports_ids))
.values(json=JSONObject(
severity='filtered_relation__severity',
report_id='id',
report_start_date='start_date',
report_end_date='end_date'
)
)
)
Таким образом, вы избежите обработки OuterRef
внутри FilteredRelation
.