为什么局部变量不能节省价值?

时间:2019-08-03 18:24:18

标签: mysql mysql5

我正在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

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

db-fiddle

这可能现在有效。但是你永远不知道,什么时候没有。考虑用程序语言解决此类任务,或升级到具有窗口功能的版本。

但是-如果强制使用“ 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

db-fiddle

答案 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()不必重复该计算。