【问题标题】:Translating MySql query into Django ORM query将 MySql 查询转换为 Django ORM 查询
【发布时间】: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 来实现它,但我希望尽可能保持抽象。

【问题讨论】:

    标签: django django-models django-admin django-orm


    【解决方案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 )
        ) )
    )
    

    【讨论】:

    【解决方案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 并将注解合二为一。请检查它是否有帮助。
    猜你喜欢
    • 2016-04-20
    • 1970-01-01
    • 2019-12-10
    • 2018-06-10
    • 2018-05-08
    • 2019-10-17
    • 2020-09-12
    • 2015-06-25
    相关资源
    最近更新 更多