这在 Django ORM 中真的不可能吗?

Posted

技术标签:

【中文标题】这在 Django ORM 中真的不可能吗?【英文标题】:is that really not possible in the Django ORM? 【发布时间】:2018-04-28 06:03:42 【问题描述】:

在我使用 Django 的 10 年中,我发现了一些错误,但我从未不得不退回到原始 SQL 查询。除了在这里寻求帮助之外,我几乎一整天都在摆弄 Django:

我有一个管理器应用程序,其中包含轮班和工作这些轮班的人员 (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) 警告关于这个确切的场景,但不知何故我没有让子查询像它应该的那样工作。

最终的和 lil 简化的 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
    )

我可能需要一个小手来实现第 4 行中的 SELECT COUNT(*) 而不是 Count(Subquery()) Django 允许我添加到查询中,但会产生 SQL 错误。

我正在使用 Python 3.6.5、Django 2.0.3 和 Postgres 10

这个问题是核心 SQL ORM 问题,所以对于任何研究这个问题的人来说都是 THX。

【问题讨论】:

【参考方案1】:

我认为这可以在没有原始 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()

【讨论】:

oooohh,我没有收到这个答案的邮件,所以我今天才看到 :( 我试过你的版本,但这给了我一个需要输出字段的错误。将它添加到新的子查询根本没有输出,因此 None 被分配给assigned_shifts。当我有更多时间时,我可能会进一步调查,谢谢你的提示!

以上是关于这在 Django ORM 中真的不可能吗?的主要内容,如果未能解决你的问题,请参考以下文章

(Django)气流中的 ORM - 有可能吗?

我真的需要 ORM 吗?

Django中的ORM转换为SQL语句日志

Django之ORM操作

06模型基础

如何查看Django ORM执行的SQL语句