Набор запросов для sql-запроса
Описание проблемы, пример данных и запрос postgres см. в Filtering unique values. Я хотел бы преобразовать SQL в набор запросов. Мне кажется, что я близок к этому, но не совсем.
SELECT Column_A, Column_B, Column_C, 0 as RN
FROM TABLE
WHERE COLUMN_C is null and Column_B in (UserA, UserB, UserC)
UNION ALL
SELECT Column_A, Column_B, Column_C, RN
FROM (
SELECT A.*, ROW_NUMBER() over (partition by A.column_C Order by case A.column_B when 'UserA' then 0 else 1 end, U.Time_Created) rn
FROM Table A
INNER JOIN user U
on U.Column_B = A.Column_B
WHERE A.Column_C is not null and ColumnB in (userA, userB, UserC)) B
WHERE RN = 1
Вот что у меня есть на данный момент:
qs1 = Table.objects.filter(Column_C__isnull=True).annotate(rn=Value(0))
qs2 = Table.objects.annotate(rn=Window(
expression=RowNumber(),
partition_by=[Column_C],
order_by=[Case(When(Column_B=UserA, then=0), default=1), 'Table_for_Column_B__time_created']
)).filter(Column_C__isnull=False, rn=1)
return qs2.union(qs1)
Это не совсем работает.
django.db.utils.NotSupportedError: Window is disallowed in the filter clause.
Далее я попробовал вытащить промежуточный результат в подзапрос, чтобы обеспечить фильтрацию во внешнем запросе, поскольку мне действительно нужны только строки с номером строки = 1.
qs1 = Table.objects.filter(Column_C__isnull=True).annotate(rn=Value(0))
qs2 = Table.objects.annotate(rn=Window(
expression=RowNumber(),
partition_by=[Column_C],
order_by=[Case(When(Column_B=UserA, then=0), default=1), 'Table_for_Column_B__time_created']
)).filter(pk=OuterRef('pk'))
qs3 = Table.objects.annotate(rn=Subquery(qs2.values('rn'))).filter(Column_C__isnull=False, rn=1)
return qs3.union(q1)
На этот раз исключений нет, но это не работает. Каждая строка в таблице получает аннотацию row_number=1. Из исходного примера queryset возвращает все 7 строк вместо фильтрации до 5.
- Is it possible to filter on window expressions?
- What's the best practices to keep in mind when converting window queries to subqueries?
- Is there a better way to structure the queryset?
Вы должны быть в состоянии сделать это без оконного выражения, используя SubQuery
Сначала создайте набор запросов для подзапроса, который упорядочивается по соответствию Column_B=UserA
, а затем time_created
from django.db.models import Case, When, Q, Subquery, OuterRef
tables_ordered = Table.objects.filter(
Column_C=OuterRef('Column_C')
).annotate(
user_match=Case(When(Column_B=UserA, then=0), default=1)
).order_by('user_match', 'time_created')
Затем этот подзапрос возвращает первый пк для совпавшего Column_C
из OuterRef
, аналогично выбору первой строки из вашей оконной функции
first_pk_for_each_column_c = Subquery(tables_ordered.values('pk')[:1])
Затем используйте два объекта Q для создания OR, который выбирает строку, если Column_C
является NULL или pk
соответствует первому pk
из подзапроса
Table.objects.filter(
Q(Column_C__isnull=True) | Q(pk=first_pk_for_each_column_c)
)