为什么局部变量不保存值?

Why local variable doesn't save value?

我正在 mysql5.7 中编写查询以模拟 dense_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

这是我的 fiddle https://www.db-fiddle.com/f/5yiyp6Zyt2eB5h26RT5Lmf/10

place in rank 的实际值为 4,3,2 但我

预计 1,1,2

在这种情况下,SELECT 在 ORDER BY 之前计算,因为没有索引可以用于 ORDER BY 子句。并且可以按任何顺序评估列(尤其是在涉及聚合函数时)。 SQL 不是程序语言。不过您可以尝试 "force" execution/evaluation 命令。在这种情况下,您(至少)需要将查询包装到一个有序的子查询中。此外 - @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

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

但是 - 如果被迫使用 "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

您需要控制评估的订单行。这可以通过使用子查询首先生成总计来完成。然后使用子查询计算排名。

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