将 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=True
combine 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 转换为整数?