重要提示:请考虑升级到 MySQL 8+ 并使用已定义并记录在案的 ROW_NUMBER() 函数,并放弃与功能受限的旧版 MySQL 相关的旧技巧
下面是其中的一个技巧:
这里大部分/全部使用查询变量的答案似乎忽略了文档所说的事实(释义):
不要依赖 SELECT 列表中的项目按从上到下的顺序进行评估。不要在一个 SELECT 项中分配变量并在另一个项中使用它们
因此,他们有可能会做出错误的答案,因为他们通常会做一个
select
(row number variable that uses partition variable),
(assign partition variable)
如果这些是自下而上评估的,行号将停止工作(无分区)
所以我们需要使用有保证执行顺序的东西。输入案例:
SELECT
t.*,
@r := CASE
WHEN col = @prevcol THEN @r + 1
WHEN (@prevcol := col) = null THEN null
ELSE 1 END AS rn
FROM
t,
(SELECT @r := 0, @prevcol := null) x
ORDER BY col
作为大纲 ld,prevcol 的分配顺序很重要 - prevcol 必须与当前行的值进行比较,然后才能从当前行为其分配一个值(否则它将是当前行的 col 值,而不是前一行的列值)。
这是如何组合在一起的:
评估第一个 WHEN。如果这一行的 col 与前一行的 col 相同,则 @r 递增并从 CASE 返回。此返回 led 值存储在 @r 中。 MySQL 的一个特性是赋值将赋值给@r 的新值返回到结果行中。
对于结果集的第一行,@prevcol 为空(在子查询中初始化为空),因此该谓词为假。每次 col 更改时,第一个谓词也会返回 false(当前行与前一行不同)。这会导致第二个 WHEN 被评估。
第二个 WHEN 谓词始终为假,它的存在纯粹是为了给 @prevcol 分配一个新值。因为这一行的 col 与前一行的 col 不同(我们知道这一点是因为如果它相同,则将使用第一个 WHEN),我们必须分配新值以保留它以供下次测试。因为进行了赋值,然后赋值的结果与 null 进行比较,任何与 null 相等的东西都是假的,所以这个谓词总是假的。但至少评估它完成了保留这一行的 col 值的工作,因此可以根据下一行的 col 值评估它
因为第二个 WHEN 为假,这意味着在我们按 (col) 分区的列发生更改的情况下,是 ELSE 为 @r 提供了一个新值,从 1 重新开始编号
我们会遇到这样的情况:
SELECT
t.*,
ROW_NUMBER() OVER(PARTITION BY pcol1, pcol2, ... pcolX ORDER BY ocol1, ocol2, ... ocolX) rn
FROM
t
有一般形式:
SELECT
t.*,
@r := CASE
WHEN col1 = @pcol1 AND col2 = @pcol2 AND ... AND colX = @pcolX THEN @r + 1
WHEN (@pcol1 := pcol1) = null OR (@pcol2 := col2) = null OR ... OR (@pcolX := colX) = null THEN null
ELSE 1
END AS rn
FROM
t,
(SELECT @r := 0, @pcol1 := null, @pcol2 := null, ..., @pcolX := null) x
ORDER BY pcol1, pcol2, ..., pcolX, ocol1, ocol2, ..., ocolX
脚注:
pcol 中的 p 表示“分区”,ocol 中的 o 表示“顺序”——在一般形式中,我从变量名中删除了“prev”以减少视觉混乱
(@pcolX := colX) = null 周围的括号很重要。没有它们,您将 null 分配给 @pcolX 并且事情停止工作
结果集也必须按分区列排序,这是一种折衷方案,以便与前一列进行比较。因此,您不能根据一列对行号进行排序,但将结果集排序到另一列您可能可以使用子查询来解决此问题,但我相信文档还指出,除非使用 LIMIT,否则子查询排序可能会被忽略,这可能会影响性能
1234563赋值)并且没有执行,它也会停止。根据我的经验,这似乎不会发生,但如果可以合理发生,我很乐意接受 cmets 并提出解决方案
在创建@pcolX 变量的子查询中,将创建@pcolX 的空值转换为列的实际类型可能是明智之举,即:select @pcol1 := CAST(null as INT), @pcol2 := CAST(null as DATE)