MySQL中的ROW_NUMBER()

时间:2009-12-12 23:58:45

标签: mysql sql greatest-n-per-group rank row-number

在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)的单行。

25 个答案:

答案 0 :(得分:193)

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

答案 1 :(得分:94)

  

对于每个(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的行。”(您将注意到这一点,并且如果多行具有相同的行,则大多数其他groupwise-maximum解决方案将返回多行col1,col2,col3。如果这是一个问题,您可能需要进行一些后期处理。)

答案 2 :(得分:78)

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

+------+------+
|    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

希望有所帮助!

答案 3 :(得分:58)

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

答案 4 :(得分: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个字段进行了测试。希望这可以帮助。查看文章,因为它打破了这个查询并解释了它。

答案 5 :(得分:18)

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

答案 6 :(得分: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,而是依赖于嵌套选择。

答案 7 :(得分: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 :(得分:8)

在MySQL中没有像rownumrow_num()这样的功能,但方法如下:

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

答案 9 :(得分:6)

查询mysql中的row_number

viewDidLoad()

答案 10 :(得分:4)

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

SELECT 
    col1, col2, 
    (
        SELECT COUNT(*) 
        FROM Table1
        WHERE col1 = t1.col1
        AND col2 = t1.col2
        AND col3 > t1.col3
    ) AS intRow
FROM Table1 t1

PARTITION BY列只是与'='进行比较并用AND分隔。 ORDER BY列将与'&lt;'进行比较或'&gt;',并以OR分隔。

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

答案 11 :(得分:4)

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

  

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

此致 的Georgi。

答案 12 :(得分:3)

MariaDB 10.2正在实现&#34; Window Functions&#34;,包括RANK(),ROW_NUMBER()和其他一些东西:

https://mariadb.com/kb/en/mariadb/window-functions/

根据Percona Live本月的一次演讲,他们已经得到了相当好的优化。

语法与问题中的代码相同。

答案 13 :(得分:2)

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

Int8 *data[4]; //array can hold maximum of 4 elements
  • ORDER BY子句必须反映您的ROW_NUMBER需求。因此,已经存在明显的限制:您不能同时对此表单进行多次ROW_NUMBER“仿真”。
  • “计算列”的顺序。如果你有mysql以另一个顺序计算这些列,它可能不起作用。
  • 在这个简单的例子中我只放了一个,但你可以有几个“PARTITION BY”部分

    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
    

答案 14 :(得分:1)

也有点晚了,但今天我有同样的需求,所以我在谷歌搜索,最后在Pinal Dave的文章http://blog.sqlauthority.com/2014/03/09/mysql-reset-row-number-for-each-group-partition-by-row-number/中找到了一个简单的一般方法

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

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

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

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

答案 15 :(得分:1)

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

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

答案 16 :(得分:1)

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

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

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

答案 17 :(得分:1)

这也可以是一个解决方案:

SET @row_number = 0;

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

答案 18 :(得分:1)

如果您的查询包含 GROUP BY 语句,则使用交叉连接和逗号的解决方案将不起作用。对于这种情况,您可以使用子选择:

SELECT (@row_number := @row_number + 1) AS rowNumber, res.*
FROM
(
  SELECT SUM(r.amount) 
  FROM Results r 
  WHERE username = 1 
  GROUP BY r.amount
) res
CROSS JOIN (SELECT @row_number := 0) AS dummy

答案 19 :(得分:0)

这里大部分/全部使用查询中变量的答案似乎忽略了文档所说的事实(解释):

  

不要依赖SELECT列表中从上到下顺序评估的项目。不要在一个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为null(在子查询中被初始化为null),因此该谓词为false。每次col更改时,该第一个谓词也返回false(当前行与上一行不同)。这将导致对第二个WHEN进行评估。

  • 第二个WHEN谓词始终为false,它的存在纯粹是为@prevcol分配一个新值。由于此行的col与上一行的col不同(我们知道这是因为如果相同,则将使用第一个WHEN),因此我们必须分配新值以使其下次进行测试。因为进行了赋值,然后将赋值的结果与null进行比较,并且等于null的任何内容均为false,所以该谓词始终为false。但是至少要评估一下,它的工作是保留此行中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 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周围的括号很重要。没有它们,您将为@pcolX分配null,事情将停止工作

  • 妥协的是,结果集也必须按分区列排序,以便比较前一列。因此,您无法将行号按一列排序,而将结果集按另一列排序。您也许可以使用子查询来解决此问题,但我相信文档也指出,除非使用LIMIT,否则子查询的排序可能会被忽略,这可能会影响性能

  • 除了测试该方法是否有效之外,我还没有深入研究它,但是如果存在第二个WHEN中的谓词将被优化的风险(与null相比的任何结果都是null / false,那么为什么还要花时间运行)分配)并且未执行,它也会停止。根据我的经验,这似乎没有发生,但我很乐意接受评论并提出解决方案,如果可能的话

  • 在创建@pcolX变量的子查询中,将创建@pcolX的空值强制转换为列的实际类型可能是明智的,即:select @pcol1 := CAST(null as INT), @pcol2 := CAST(null as DATE)

答案 20 :(得分:0)

自版本 8.0 + 起,MySQL已支持ROW_NUMBER()

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

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

对于旧版本,

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

答案 21 :(得分:0)

这不是最可靠的解决方案-但是,如果您只是想在仅包含几个不同值的字段上创建分区等级,那么在逻辑中使用与您一样多的变量时,可能会不明智地使用这种情况要求。

过去类似的事情对我有用:

SELECT t.*, 
   CASE WHEN <partition_field> = @rownum1 := @rownum1 + 1 
     WHEN <partition_field> = @rownum2 := @rownum2 + 1 
     ...
     END AS rank
FROM YOUR_TABLE t, 
   (SELECT @rownum1 := 0) r1, (SELECT @rownum2 := 0) r2
ORDER BY <rank_order_by_field>
;

有希望/有帮助的希望!

答案 22 :(得分:-1)

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;

答案 23 :(得分:-1)

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

SELECT @row_num := IF(@prev_value= concat(`Fk_Business_Unit_Code`,`NetIQ_Job_Code`), @row_num+1, 1) AS RowNumber, 
    `Fk_Business_Unit_Code`,   
    `NetIQ_Job_Code`,  
    `Supervisor_Name`,  
    @prev_value := concat(`Fk_Business_Unit_Code`,`NetIQ_Job_Code`)  
FROM (SELECT DISTINCT `Fk_Business_Unit_Code`,`NetIQ_Job_Code`,`Supervisor_Name`         
      FROM Employee    
      ORDER BY `Fk_Business_Unit_Code`, `NetIQ_Job_Code`, `Supervisor_Name` DESC) z,  
(SELECT @row_num := 1) x,  
(SELECT @prev_value := '') y  
ORDER BY `Fk_Business_Unit_Code`, `NetIQ_Job_Code`,`Supervisor_Name` DESC

答案 24 :(得分:-5)

SELECT 
    col1, col2, 
    count(*) as intRow
FROM Table1
GROUP BY col1,col2
ORDER BY col3 desc