我正在mysql5.7中编写查询以模拟density_rank()。我对变量的范围有疑问。
我尝试过以不同的方式使用变量,但是总是错误地计算在排名中。似乎变量@total
设置错误。
SET @row_number = 1;
SET @total =null;
SELECT
CONCAT(`u`.`name`, ' ' ,`u`.`surname`) as `User`,
SUM(`oi`.quantity * `oi`.price) as `Total amount`,
CASE
WHEN @total = SUM(`oi`.quantity * `oi`.price) THEN
@row_number
ELSE
@row_number:= @row_number + 1
END
as `Place in rank`,
@total := SUM(`oi`.quantity * `oi`.price)
FROM `user` u
LEFT JOIN `order` o ON `u`.`user_id`=`o`.user_id
LEFT JOIN `order_item` oi ON `oi`.`order_id`=`o`.order_id
WHERE `o`.`date` > DATE_SUB(CURDATE(), INTERVAL 3 MONTH)
GROUP BY user
ORDER BY `Total amount` DESC
这是我的小提琴https://www.db-fiddle.com/f/5yiyp6Zyt2eB5h26RT5Lmf/10
列place in rank
的实际值为4,3,2,但I
期望1,1,2
答案 0 :(得分:0)
在这种情况下,SELECT是在ORDER BY之前进行求值的,因为ORDER BY子句无法使用任何索引。并且可以按任何顺序评估列(尤其是在涉及聚合函数时)。 SQL不是过程语言。但是,您可以尝试“强制”执行/评估顺序。在这种情况下,您需要(至少)将查询包装到有序子查询中。另外-@row_number
应该初始化为0
:
SET @row_number = 0;
SET @total = null;
SELECT *,
CASE WHEN @total = `Total amount`
THEN @row_number
ELSE @row_number:= @row_number + 1
END AS `Place in rank`,
@total := `Total amount`
FROM (
SELECT
CONCAT(`u`.`name`, ' ' ,`u`.`surname`) as `User`,
SUM(`oi`.quantity * `oi`.price) as `Total amount`
FROM `user` u
LEFT JOIN `order` o ON `u`.`user_id`=`o`.user_id
LEFT JOIN `order_item` oi ON `oi`.`order_id`=`o`.order_id
WHERE `o`.`date` > DATE_SUB(CURDATE(), INTERVAL 3 MONTH)
GROUP BY user
ORDER BY `Total amount` DESC
) x
这可能现在有效。但是你永远不知道,什么时候没有。考虑用程序语言解决此类任务,或升级到具有窗口功能的版本。
但是-如果强制使用“ SQL”,我会以不同的方式编写查询:
SELECT x.*,
CASE WHEN @total = `Total amount`
THEN @row_number
ELSE @row_number:= @row_number + 1 + 0*(@total := `Total amount`)
END AS `Place in rank`
FROM (
SELECT
CONCAT(`u`.`name`, ' ' ,`u`.`surname`) as `User`,
SUM(`oi`.quantity * `oi`.price) as `Total amount`
FROM `user` u
LEFT JOIN `order` o ON `u`.`user_id`=`o`.user_id
LEFT JOIN `order_item` oi ON `oi`.`order_id`=`o`.order_id
WHERE `o`.`date` > DATE_SUB(CURDATE(), INTERVAL 3 MONTH)
GROUP BY user
ORDER BY `Total amount` DESC
) x
CROSS JOIN (SELECT @row_number := 0, @total := null) init_vars
答案 1 :(得分:0)
您需要控制要评估的订单行。这可以通过使用子查询首先生成总数来完成。然后使用子查询计算排名。
set @last_total = null;
set @rank = 0;
select *,
CASE
WHEN `total amount` = @last_total THEN
@rank
ELSE
@rank := @rank + 1
END
as `Place in rank`,
@last_total := `total amount`
from (
SELECT
CONCAT(`u`.`name`, ' ' ,`u`.`surname`) as `User`,
SUM(`oi`.quantity * `oi`.price) as `Total amount`
FROM `user` u
LEFT JOIN `order` o ON `u`.`user_id`=`o`.user_id
LEFT JOIN `order_item` oi ON `oi`.`order_id`=`o`.order_id
WHERE `o`.`date` > DATE_SUB(CURDATE(), INTERVAL 3 MONTH)
GROUP BY `user`
) user_totals
order by `total amount` desc;
如果您可以使用MySQL 8,它具有dense_rank
的功能,可以为您完成所有这些工作。
with user_totals as (
SELECT
CONCAT(`u`.`name`, ' ' ,`u`.`surname`) as `User`,
SUM(`oi`.quantity * `oi`.price) as `Total amount`
FROM `user` u
LEFT JOIN `order` o ON `u`.`user_id`=`o`.user_id
LEFT JOIN `order_item` oi ON `oi`.`order_id`=`o`.order_id
WHERE `o`.`date` > DATE_SUB(CURDATE(), INTERVAL 3 MONTH)
GROUP BY `User`
)
select *,
dense_rank() over( order by `Total amount` desc )
from user_totals;
在这里,我用Common Table Expression拆分了查询以计算总数,从而使dense_rank()
不必重复该计算。