За 10 лет игры с Django я обнаружил несколько ошибок, но мне никогда не приходилось возвращаться к необработанному SQL-запросу. Возиться с Джанго в течение почти целого дня, теперь у меня нет идей, кроме просьбы о помощи здесь:
У меня есть приложение-органайзер с днями, в которые входят смены и люди, работающие в эти смены (M2M). Также есть супервизоры для каждого дня, которые хранятся непосредственно в день (M2M). Для статистики пользователя я хочу видеть, у кого сколько смен. По соображениям скорости я ХОЧУ справиться с этим только одним запросом Вот запрос, с которым я закончил:
assigned_to = self.shifts.filter(day_id=OuterRef('id')).order_by()
days = Day.data.filter(date__range=(start, end)).distinct().annotate(
assigned_shifts=Count(
Subquery(assigned_to.values('id'))
)).annotate(
assigned_pl=Max(Case(
When(production_managers=self, then=1),
default=0, output_field=models.IntegerField(),
distinct=True
))).annotate(
assigned_tl=Max(Case(
When(day_managers=self, then=1),
default=0, output_field=models.IntegerField(), distinct=True
))).annotate(
assigned_al=Max(Case(
When(managers=self, then=1),
default=0, output_field=models.IntegerField(), distinct=True
))).annotate(
assigned_cash=Max(Case(
When(cash_managers=self, then=1),
default=0, output_field=models.IntegerField(), distinct=True
))).order_by()
Это дает мне ошибку SQL "более одной строки, возвращенной подзапросом, используемым в качестве выражения", tho. Без подзапроса я получал ложные значения - вероятно, потому, что группировка не сработала так, как предполагалось. Оказывается, в документах есть предупреждение и очень старый тикет (https://code.djangoproject.com/ticket/10060) предупреждение об этом точном сценарии, но почему-то я не могу заставить подзапрос работать так, как должен.
Окончательный и упрощенный SQL выглядит следующим образом:
days = Day.data.raw("""
SELECT distinct
"events_day"."id","events_day"."date",
(select count(*)
from events_shift
left outer join events_shift_employees
on events_shift_employees.shift_id = events_shift.id
where events_shift_employees.user_id = {user_id} and
events_shift.day_id = events_day.id and
events_shift.deleted is null) AS "assigned_shifts",
Max(CASE WHEN"events_day_production_managers"."user_id"={user_id} THEN 1 ELSE 0 END)AS"assigned_pl",
Max(CASE WHEN"events_day_day_managers"."user_id"={user_id} THEN 1 ELSE 0 END)AS"assigned_tl",
Max(CASE WHEN"events_day_managers"."user_id"={user_id} THEN 1 ELSE 0 END)AS"assigned_al",
max(CASE WHEN"events_day_cash_managers"."user_id"={user_id} THEN 1 ELSE 0 END)AS"assigned_cash"
FROM"events_day"
LEFT JOIN"events_shift"ON("events_day"."id"="events_shift"."day_id")
LEFT JOIN"events_shift_employees"ON("events_shift"."id"="events_shift_employees"."shift_id")
LEFT JOIN"events_day_production_managers"ON("events_day"."id"="events_day_production_managers"."day_id")
LEFT JOIN"events_day_day_managers"ON("events_day"."id"="events_day_day_managers"."day_id")
LEFT JOIN"events_day_managers"ON("events_day"."id"="events_day_managers"."day_id")
LEFT JOIN"events_day_cash_managers"ON("events_day"."id"="events_day_cash_managers"."day_id")
WHERE("events_day"."deleted"IS NULL AND"events_day"."date" BETWEEN '{start}' AND '{end}')
GROUP BY "events_day"."id"
ORDER BY"events_day"."date"DESC""".format(**{
'user_id': self.id,
'start': start,
'end': end
})
Мне может понадобиться небольшая рука для достижения SELECT COUNT (*) в строке 4, а не Count (Subquery ()), которую Django позволяет мне добавить в запрос, но выдает ошибку SQL.
Я работаю на Python 3.6.5, Django 2.0.3 и Postgres 10
Эта проблема - хардкорные SQL ORM, так что THX для тех, кто занимается этим.