MySQL中的ROW_NUMBER()

问题描述 投票:252回答:23

在MySQL中有一个很好的方法来复制SQL Server函数ROW_NUMBER()吗?

例如:

SELECT 
    col1, col2, 
    ROW_NUMBER() OVER (PARTITION BY col1, col2 ORDER BY col3 DESC) AS intRow
FROM Table1

然后,我可以,例如,添加一个条件,将intRow限制为1,以获得每个col3对具有最高(col1, col2)的单行。

mysql sql greatest-n-per-group rank row-number
23个回答
97
投票

我希望每个(col1,col2)对的行具有单个最高col3。

这是一个groupwise maximum,这是最常见的SQL问题之一(因为它看起来应该很简单,但实际上并非如此)。

我常常喜欢null-self-join:

SELECT t0.col3
FROM table AS t0
LEFT JOIN table AS t1 ON t0.col1=t1.col1 AND t0.col2=t1.col2 AND t1.col3>t0.col3
WHERE t1.col1 IS NULL;

“获取表格中没有其他行匹配col1的行,col2具有更高的col3。”(您会注意到这一点,如果多行具有相同的col1,则大多数其他groupwise-maximum解决方案将返回多行,col2 ,col3。如果这是一个问题,你可能需要一些后期处理。)


6
投票

查询mysql中的row_number

row_num()

4
投票

我发现最好的解决方案是使用这样的子查询:

select 
      @s:=@s+1 serial_no, 
      tbl.* 
from my_table tbl, (select @s:=0) as s;

PARTITION BY列只是与'='进行比较并用AND分隔。 ORDER BY列将与“<”或“>”进行比较,并以OR分隔。

我发现这很灵活,即使它有点贵。


4
投票

无法模仿rownumber功能。您可能会得到您期望的结果,但您很可能会在某个阶段感到失望。这是mysql文档所说的:

对于其他语句,例如SELECT,您可能会得到您期望的结果,但这不能保证。在下面的语句中,您可能会认为MySQL将首先评估@a然后再进行一次赋值:SELECT @ a,@ a:= @ a + 1,...;但是,涉及用户变量的表达式的评估顺序是未定义的。

此致,乔治。


3
投票

MariaDB 10.2正在实现“窗口函数”,包括RANK(),ROW_NUMBER()和其他一些东西:

set @row_number=0; select (@row_number := @row_number +1) as num,id,name from sbs

根据Percona Live本月的演讲,他们进行了相当好的优化。

语法与Question中的代码相同。


2
投票

我没有看到任何关于“PARTITION BY”部分的简单答案,所以这是我的:

SELECT 
    col1, col2, 
    (
        SELECT COUNT(*) 
        FROM Table1
        WHERE col1 = t1.col1
        AND col2 = t1.col2
        AND col3 > t1.col3
    ) AS intRow
FROM Table1 t1
  • ORDER BY子句必须反映您的ROW_NUMBER需求。因此,已经存在明显的限制:您不能同时对此表单进行多次ROW_NUMBER“仿真”。
  • “计算列”的顺序很重要。如果你有mysql以其他顺序计算这些列,它可能无法正常工作。
  • 在这个简单的例子中我只放了一个,但你可以有几个“PARTITION BY”部分 https://mariadb.com/kb/en/mariadb/window-functions/

1
投票

有点晚,但也可能帮助寻找答案的人...

在rows / row_number示例之间 - 可以在任何SQL中使用的递归查询:

SELECT
    *
FROM (
    select
        CASE WHEN @partitionBy_1 = l THEN @row_number:=@row_number+1 ELSE @row_number:=1 END AS i
        , @partitionBy_1:=l AS p
        , t.*
    from (
        select @row_number:=0,@partitionBy_1:=null
    ) as x
    cross join (
        select 1 as n, 'a' as l
        union all
        select 1 as n, 'b' as l    
        union all
        select 2 as n, 'b' as l    
        union all
        select 2 as n, 'a' as l
        union all
        select 3 as n, 'a' as l    
        union all    
        select 3 as n, 'b' as l    
    ) as t
    ORDER BY l, n
) AS X
where i > 1

1
投票

这允许在MySQL中实现与ROW_NUMBER()和PARTITION BY相同的功能

    CASE WHEN @partitionBy_1 = part1 AND @partitionBy_2 = part2 [...] THEN @row_number:=@row_number+1 ELSE @row_number:=1 END AS i
    , @partitionBy_1:=part1 AS P1
    , @partitionBy_2:=part2 AS P2
    [...] 
FROM (
    SELECT @row_number:=0,@partitionBy_1:=null,@partitionBy_2:=null[...]
) as x

1
投票

也有点晚了,但今天我有同样的需求,所以我在谷歌搜索,最后在Pinal Dave的文章WITH data(row_num, some_val) AS ( SELECT 1 row_num, 1 some_val FROM any_table --dual in Oracle UNION ALL SELECT row_num+1, some_val+row_num FROM data WHERE row_num < 20 -- any number ) SELECT * FROM data WHERE row_num BETWEEN 5 AND 10 / ROW_NUM SOME_VAL ------------------- 5 11 6 16 7 22 8 29 9 37 10 46 中找到了一个简单的通用方法

我想专注于保罗的原始问题(这也是我的问题)所以我总结了我的解决方案作为一个工作的例子。

因为我们想要对两列进行分区,所以我会在迭代期间创建一个SET变量,以确定是否已启动新组。

SELECT  @row_num := IF(@prev_value=GENDER,@row_num+1,1) AS RowNumber
       FirstName, 
       Age,
       Gender,
       @prev_value := GENDER
  FROM Person,
      (SELECT @row_num := 1) x,
      (SELECT @prev_value := '') y
  ORDER BY Gender, Age DESC

3表示MAKE_SET的第一个参数,我想要SET中的两个值(3 = 1 | 2)。当然,如果我们没有两列或更多列构造组,我们可以消除MAKE_SET操作。结构完全一样。这对我来说很有用。非常感谢Pinal Dave的明确演示。



1
投票

从版本8.0+开始,MySQL支持ROW_NUMBER()。

如果您使用MySQL 8.0或更高版本,请检查ROW_NUMBER()函数。否则,您已模拟ROW_NUMBER()函数。

row_number()是一个排名函数,它返回一行的序号,从第一行的1开始。

对于旧版本,

SELECT col1, col2, col3 FROM (
  SELECT col1, col2, col3,
         @n := CASE WHEN @v = MAKE_SET(3, col1, col2)
                    THEN @n + 1 -- if we are in the same group
                    ELSE 1 -- next group starts so we reset the counter
                END AS row_number,
         @v := MAKE_SET(3, col1, col2) -- we store the current value for next iteration
    FROM Table1, (SELECT @n := 0, @v := NULL) r -- helper table for iteration with startup values
   ORDER BY col1, col2, col3 DESC -- because we want the row with maximum value
) x WHERE row_number = 1 -- and here we select exactly the wanted row from each group

197
投票

MySQL中没有排名功能。你可以得到的最接近的是使用一个变量:

SELECT t.*, 
       @rownum := @rownum + 1 AS rank
  FROM YOUR_TABLE t, 
       (SELECT @rownum := 0) r

那么在我的案例中如何运作呢?我需要两个变量,col1和col2各有一个?当col1改变时,Col2需要以某种方式重置..?

是。如果是Oracle,则可以使用LEAD函数在下一个值处达到峰值。值得庆幸的是,Quassnoi涵盖了the logic for what you need to implement in MySQL


0
投票

重要提示:请考虑升级到MySQL 8+并使用定义和记录的ROW_NUMBER()函数,并抛弃绑定到功能有限的古代MySQL版本的旧hacks

现在这里是其中一个黑客:

这里使用查询变量的答案大多数/全都似乎忽略了文档所说的事实(释义):

不要依赖于从上到下按顺序评估的SELECT列表中的项目。不要在一个SELECT项中分配变量,而在另一个SELECT项中使用它们

因此,他们会冒出错误答案的风险,因为他们通常会这样做

SET @row_number = 0;

SELECT 
    (@row_number:=@row_number + 1) AS num, firstName, lastName
FROM
    employees

如果从下到上评估这些,行号将停止工作(没有分区)

所以我们需要使用保证执行顺序的东西。输入CASE WHEN:

SELECT t.*, 
       @rowid := @rowid + 1 AS ROWID
  FROM TABLE t, 
       (SELECT @rowid := 0) dummy;

作为概述ld,prevcol的赋值顺序很重要 - 在我们为当前行分配值之前,必须将prevcol与当前行的值进行比较(否则它将是当前行的col值,而不是前一行的col值) 。

这是如何组合在一起的:

  • 评估第一个WHEN。如果此行的col与前一行的col相同,则@r将递增并从CASE返回。此返回值包含在@r中。 MySQL的一个特性是赋值将分配给@r的新值返回到结果行中。
  • 对于结果集的第一行,@ prevcol为null(在子查询中将其初始化为null),因此该谓词为false。每次col更改时,此第一个谓词也返回false(当前行与前一行不同)。这导致第二个WHEN被评估。
  • 第二个WHEN谓词总是错误的,它纯粹是为了给@prevcol赋一个新值。因为这行的col与前一行的col不同(我们知道这是因为如果它是相同的,将使用第一个WHEN),我们必须分配新值以保留它以便下次测试。由于进行了赋值,然后将赋值结果与null进行比较,并且等于null的任何内容都为false,因此该谓词始终为false。但至少评估它是否能够保持col的值来自这一行,因此可以根据下一行的col值进行评估
  • 因为第二个WHEN是假的,这意味着在我们通过(col)分区的列发生了变化的情况下,它是为@r提供新值的ELSE,从1重新开始编号

我们遇到这样的情况:

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

脚注:

  • pcol中的p表示“分区”,ocol中的o表示“顺序” - 在一般形式中,我从变量名中删除了“prev”以减少视觉混乱
  • SELECT t.*, ROW_NUMBER() OVER(PARTITION BY pcol1, pcol2, ... pcolX ORDER BY ocol1, ocol2, ... ocolX) rn FROM t 周围的括号非常重要。如果没有它们,你将为@pcolX分配null并且事情停止工作
  • 这是一个妥协,结果集也必须由分区列排序,因为前一列比较结果。因此,您不能根据一列排序您的rownumber,但是您的结果集已经排序到另一列您可能能够使用子查询解决此问题,但我相信文档还声明除非使用LIMIT,否则可能会忽略子查询排序,这可能会影响性能
  • 除了测试该方法是否有效之外,我还没有深入研究它,但是如果存在第二个WHEN中的谓词将被优化掉的风险(与null相比的任何东西是null / false,那么为什么还要运行赋值)并且不执行,它也停止了。这似乎不符合我的经验,但我很乐意接受评论并提出解决方案,如果它可以合理地发生
  • 在创建@pcolX变量的子查询中,将创建@pcolX的空值转换为列的实际类型可能是明智的,即: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

-1
投票
(@pcolX := colX) = null

-1
投票

当我们有多个列时,这对我来说非常适合创建RowNumber。在这种情况下两列。

select @pcol1 := CAST(null as INT), @pcol2 := CAST(null as DATE)

-5
投票
set @i = 1;  
INSERT INTO ARG_VALUE_LOOKUP(ARG_VALUE_LOOKUP_ID,ARGUMENT_NAME,VALUE,DESCRIPTION,UPDATE_TIMESTAMP,UPDATE_USER,VER_NBR,OBJ_ID) 
select @i:= @i+1 as ARG_VALUE_LOOKUP_ID,ARGUMENT_NAME,VALUE,DESCRIPTION,CURRENT_TIMESTAMP,'admin',1,UUID() 
FROM TEMP_ARG_VALUE_LOOKUP 
order by ARGUMENT_NAME;

80
投票

我总是最终遵循这种模式。鉴于此表:

+------+------+
|    i |    j |
+------+------+
|    1 |   11 |
|    1 |   12 |
|    1 |   13 |
|    2 |   21 |
|    2 |   22 |
|    2 |   23 |
|    3 |   31 |
|    3 |   32 |
|    3 |   33 |
|    4 |   14 |
+------+------+

你可以得到这个结果:

+------+------+------------+
|    i |    j | row_number |
+------+------+------------+
|    1 |   11 |          1 |
|    1 |   12 |          2 |
|    1 |   13 |          3 |
|    2 |   21 |          1 |
|    2 |   22 |          2 |
|    2 |   23 |          3 |
|    3 |   31 |          1 |
|    3 |   32 |          2 |
|    3 |   33 |          3 |
|    4 |   14 |          1 |
+------+------+------------+

通过运行此查询,不需要定义任何变量:

SELECT a.i, a.j, count(*) as row_number FROM test a
JOIN test b ON a.i = b.i AND a.j >= b.j
GROUP BY a.i, a.j

希望有所帮助!


58
投票
SELECT 
    @i:=@i+1 AS iterator, 
    t.*
FROM 
    tablename AS t,
    (SELECT @i:=0) AS foo

26
投票

查看这篇文章,它展示了如何在MySQL中使用分区模仿SQL ROW_NUMBER()。我在WordPress实现中遇到了同样的情况。我需要ROW_NUMBER()并且它不存在。

http://www.explodybits.com/2011/11/mysql-row-number/

本文中的示例使用单个分区字段。要通过其他字段进行分区,您可以执行以下操作:

  SELECT  @row_num := IF(@prev_value=concat_ws('',t.col1,t.col2),@row_num+1,1) AS RowNumber
         ,t.col1 
         ,t.col2
         ,t.Col3
         ,t.col4
         ,@prev_value := concat_ws('',t.col1,t.col2)
    FROM table1 t,
         (SELECT @row_num := 1) x,
         (SELECT @prev_value := '') y
   ORDER BY t.col1,t.col2,t.col3,t.col4 

使用concat_ws处理null。我使用int,date和varchar对3个字段进行了测试。希望这可以帮助。查看文章,因为它打破了这个查询并解释它。


21
投票

MySQL 8.0.0及以上,你可以原生使用窗口函数。

1.4 What Is New in MySQL 8.0

窗口功能。

MySQL现在支持窗口函数,对于查询中的每一行,它使用与该行相关的行执行计算。这些包括RANK(),LAG()和NTILE()等函数。此外,现在可以将几个现有的聚合函数用作窗函数;例如,SUM()和AVG()。

ROW_NUMBER() over_clause

返回其分区中当前行的编号。行数从1到分区行数。

ORDER BY影响行的编号顺序。没有ORDER BY,行编号是不确定的。

演示:

CREATE TABLE Table1(
  id INT AUTO_INCREMENT PRIMARY KEY, col1 INT,col2 INT, col3 TEXT);

INSERT INTO Table1(col1, col2, col3)
VALUES (1,1,'a'),(1,1,'b'),(1,1,'c'),
       (2,1,'x'),(2,1,'y'),(2,2,'z');

SELECT 
    col1, col2,col3,
    ROW_NUMBER() OVER (PARTITION BY col1, col2 ORDER BY col3 DESC) AS intRow
FROM Table1;

DBFiddle Demo


15
投票

我还投票支持Mosty Mostacho的解决方案,对他的查询代码进行了少量修改:

SELECT a.i, a.j, (
    SELECT count(*) from test b where a.j >= b.j AND a.i = b.i
) AS row_number FROM test a

哪个会得到相同的结果:

+------+------+------------+
|    i |    j | row_number |
+------+------+------------+
|    1 |   11 |          1 |
|    1 |   12 |          2 |
|    1 |   13 |          3 |
|    2 |   21 |          1 |
|    2 |   22 |          2 |
|    2 |   23 |          3 |
|    3 |   31 |          1 |
|    3 |   32 |          2 |
|    3 |   33 |          3 |
|    4 |   14 |          1 |
+------+------+------------+

对于表:

+------+------+
|    i |    j |
+------+------+
|    1 |   11 |
|    1 |   12 |
|    1 |   13 |
|    2 |   21 |
|    2 |   22 |
|    2 |   23 |
|    3 |   31 |
|    3 |   32 |
|    3 |   33 |
|    4 |   14 |
+------+------+

唯一的区别是查询不使用JOIN和GROUP BY,而是依赖于嵌套选择。


11
投票

我会定义一个函数:

delimiter $$
DROP FUNCTION IF EXISTS `getFakeId`$$
CREATE FUNCTION `getFakeId`() RETURNS int(11)
    DETERMINISTIC
begin
return if(@fakeId, @fakeId:=@fakeId+1, @fakeId:=1);
end$$

然后我可以这样做:

select getFakeId() as id, t.* from table t, (select @fakeId:=0) as t2;

现在您没有子视图,视图中没有子查询。


8
投票

在MySQL中没有像qazxsw poi,qazxsw poi这样的函数,但是如下所示:

rownum
© www.soinside.com 2019 - 2024. All rights reserved.