это действительно невозможно в Django ORM? - PullRequest
0 голосов
/ 28 апреля 2018

За 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 для тех, кто занимается этим.

1 Ответ

0 голосов
/ 28 апреля 2018

Я думаю, что это может быть достигнуто без необработанного SQL с небольшим изменением вашего подзапроса.

assigned_to = self.shifts.filter(day_id=OuterRef('id')).order_by().values('day_id')
count_assigned = assigned_to.annotate(count=Count('*')).values('count')
days = Day.data.filter(date__range=(start, end)).distinct().annotate(
    assigned_shifts=Subquery(count_assigned)
    ).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()
...