将 MySql 查询转换为 Django ORM 查询

Posted

技术标签:

【中文标题】将 MySql 查询转换为 Django ORM 查询【英文标题】:Translating MySql query into Django ORM query 【发布时间】:2016-06-04 15:23:12 【问题描述】:

我在 mysql 中有一个查询,我需要将其翻译成 Django ORM。它涉及加入两个表,其中一个表有两个计数。我在 Django 中非常接近它,但我得到了重复的结果。这是查询:

SELECT au.id, 
       au.username, 
       COALESCE(orders_ct, 0) AS orders_ct, 
       COALESCE(clean_ct, 0)  AS clean_ct, 
       COALESCE(wash_ct, 0)   AS wash_ct 
FROM   auth_user AS au 
       LEFT OUTER JOIN 
           ( SELECT user_id,
                    Count(*)  AS orders_ct                    
             FROM   `order`
             GROUP  BY user_id
           ) AS o 
                    ON au.id = o.user_id 
       LEFT OUTER JOIN  
           ( SELECT user_id,
                    Count(CASE WHEN service = 'clean' THEN 1 
                          END)  AS clean_ct,
                    Count(CASE WHEN service = 'wash' THEN 1 
                          END)  AS wash_ct                            
             FROM   job
             GROUP  BY user_id
           ) AS j
                    ON au.id = j.user_id 
ORDER  BY au.id DESC 
LIMIT  100 ;

我当前的 Django 查询(带回不需要的重复项):

User.objects.annotate(
    orders_ct = Count( 'orders', distinct = True )
).annotate(
    clean_ct = Count( Case(
        When( job__service__exact = 'clean', then = 1 )
    ) )
).annotate(
    wash_ct = Count( Case(
        When( job__service__exact = 'wash', then = 1 )
    ) )
)

上面的 Django 代码产生了以下查询,它是接近但不正确的:

SELECT DISTINCT `auth_user`.`id`, 
                `auth_user`.`username`, 
                Count(DISTINCT `order`.`id`) AS `orders_ct`, 
                Count(CASE 
                        WHEN `job`.`service` = 'clean' THEN 1 
                        ELSE NULL 
                      end)                   AS `clean_ct`, 
                Count(CASE 
                        WHEN `job`.`service` = 'wash' THEN 1 
                        ELSE NULL 
                      end)                   AS `wash_ct` 
FROM   `auth_user` 
       LEFT OUTER JOIN `order` 
                    ON ( `auth_user`.`id` = `order`.`user_id` ) 
       LEFT OUTER JOIN `job` 
                    ON ( `auth_user`.`id` = `job`.`user_id` ) 
GROUP  BY `auth_user`.`id` 
ORDER  BY `auth_user`.`id` DESC 
LIMIT  100 

我可能可以通过做一些raw sql subqueries 来实现它,但我希望尽可能保持抽象。

【问题讨论】:

【参考方案1】:

基于this answer,可以写:

User.objects.annotate(
    orders_ct = Count( 'orders', distinct = True ),
    clean_ct = Count( Case(
        When( job__service__exact = 'clean', then = F('job__pk') )
    ), distinct = True ),
    wash_ct = Count( Case(
        When( job__service__exact = 'wash', then = F('job__pk') )
    ), distinct = True )
)

表(连接后):

user.id  order.id  job.id  job.service  your case/when  my case/when
1        1         1       wash         1               1
1        1         2       wash         1               2
1        1         3       clean        NULL            NULL
1        1         4       other        NULL            NULL
1        2         1       wash         1               1
1        2         2       wash         1               2
1        2         3       clean        NULL            NULL
1        2         4       other        NULL            NULL

wash_ct 的期望输出是 2。计算 my case/when 中的不同值,我们将得到 2。

【讨论】:

这将使我的所有计数变为 0 或 1,因为我计算不同的值,这意味着它将消除其他所有内容,因此它只会计算一条记录或没有记录。 @RomeoMihalcea 已添加表格。 我之前尝试过不同的计数,但没有用。 F thingie 做到了。【参考方案2】:

我认为这会起作用,作业的链接注释可能会产生重复的用户。

如果不能,您能否详细说明您看到的重复项。

User.objects.annotate(
    orders_ct = Count( 'orders', distinct = True )
).annotate(
    clean_ct = Count( Case(
        When( job__service__exact = 'clean', then = 1 )
    ) ),
    wash_ct = Count( Case(
        When( job__service__exact = 'wash', then = 1 )
    ) )
)

【讨论】:

产生相同的查询。没有什么可详细说明的,由于两个左外连接,django 的查询会产生重复项。请参阅此已接受的答案:dba.stackexchange.com/questions/129982/…【参考方案3】:

尝试添加values(),也可以添加distinct=Truecombine Count()'s in one annotation()

Users.objects.values("id").annotate(
    orders_ct = Count('orders', distinct = True)
    ).annotate(
    clean_ct = Count(Case(When(job__service__exact='clean', then=1)),
               distinct = True),
    wash_ct = Count(Case(When(job__service__exact='wash',then=1)),
              distinct = True)
    ).values("id", "username", "orders_ct", "clean_ct", "wash_сt")

使用values("id") 应该为注释添加GROUP BY 'id',从而防止重复,请参阅docs。

另外,还有Coalesce,但它看起来并不需要,因为Count() 无论如何都会返回int。还有distinct,但是Count() 中的distinct 应该足够了。

不确定Case 是否需要在Count() 中,因为无论如何它都应该计算它们。

【讨论】:

我认为你不能在 Django 中这样计算:TypeError: __init__() missing 1 required positional argument: 'expression' @Romeo Mihalcea,修改了查询,使其看起来更像原始查询,Count 是您声称的工作。我的重点不在Count 本身,而是使用values 获取GROUP BY 并将注解合二为一。请检查它是否有帮助。

以上是关于将 MySql 查询转换为 Django ORM 查询的主要内容,如果未能解决你的问题,请参考以下文章

在 django ORM 中查询时如何将 char 转换为整数?

Django ORM查询,不同的值并加入同一张表

django -2 ORM模型

django基础知识之ORM简介:

将遗留的 mySQL 数据库集成到新的 Django ORM 驱动的数据结构中

django 模型