Номер строки () в MySQL

есть ли хороший способ в 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 для каждого

21 ответов


Я хочу строку с одним самым высоким col3 для каждой пары (col1, col2).

Это groupwise для максимальной один из наиболее часто задаваемых вопросов по 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-максимальные решения вернут несколько строк, если несколько строк имеют одинаковый col1, col2, col3. Если это проблема, вам может понадобиться некоторая пост-обработка.)


в MySQL нет функции ранжирования. Самое близкое, что вы можете получить, это использовать переменную:

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

Итак, как это будет работать в моем случае? Мне нужны две переменные, по одной для каждого из col1 и col2? Col2 нужно будет каким-то образом сбросить, когда col1 изменится..?

да. Если бы это был Oracle, вы могли бы использовать функцию LEAD для пика при следующем значении. К счастью, Quassnoi крышки логика для того, что вам нужно реализовать в В MySQL.


Я всегда в конечном итоге следую этой схеме. Учитывая эту таблицу:

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

надеюсь, что это поможет!


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

проверьте эту статью, она показывает, как имитировать SQL ROW_NUMBER() с разделом в MySQL. Я столкнулся с этим же сценарием в реализации 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. Я протестировал это против 3 полей, используя int, date и varchar. Надеюсь, это поможет. Проверьте статью, поскольку она разбивает этот запрос и объясняет его.


Я бы также проголосовал за решение мости Мостачо с незначительной модификацией его кода запроса:

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, полагаясь на вложенный select.


С MySQL 8.0.0 и выше вы можете изначально использовать оконные функции.

1.4 что нового в 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


Я бы определил функции:

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;

теперь у вас нет подзапросов, которые вы не можете иметь в вид.


там нет funtion, как rownum, row_num() в MySQL, но путь вокруг, как показано ниже:

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

запрос для row_number в mysql

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

решение, которое я нашел для работы лучше всего, использовало такой подзапрос:

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

раздел по столбцам просто сравнивается с " = "и разделяется на "и". Порядок по столбцам будет сравниваться с " "и разделяться "или".

Я нашел это очень гибким, даже если это немного дорого.


функциональность rownumber не может быть имитирована. Вы можете получить ожидаемые результаты, но на каком-то этапе вы, скорее всего, будете разочарованы. Вот что говорит документация mysql:

для других операторов, таких как SELECT, вы можете получить ожидаемые результаты, но это не гарантируется. В следующем заявлении вы можете подумать, что MySQL сначала оценит @a, а затем выполнит второе назначение: Выберите @a, @a:=@a+1,...; Однако, порядок оценки выражения, включающие пользовательские переменные, не определены.

с уважением, Георгий.


MariaDB 10.2 реализует "оконные функции", включая RANK (), ROW_NUMBER() и несколько других вещей:

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

основываясь на разговоре в Percona Live в этом месяце, они достаточно хорошо оптимизированы.

синтаксис идентичен коду в вопросе.


немного поздно, но может также помочь тем, кто ищет ответы...

Between rows / row_number example-рекурсивный запрос, который может использоваться в любом 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

Это позволяет той же функциональности, что ROW_NUMBER () и PARTITION BY обеспечивает быть достигнутым в MySQL

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

также немного поздно, но сегодня у меня была такая же потребность, поэтому я искал в Google и, наконец, простой общий подход, найденный здесь в статье Pinal Davehttp://blog.sqlauthority.com/2014/03/09/mysql-reset-row-number-for-each-group-partition-by-row-number/

Beacuse мы хотим разбить на два столбца, я бы создал Задайте переменную во время итерации, чтобы определить, была ли запущена новая группа.

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 я хочу оба значения в наборе (3=1/2). Конечно, если у нас нет двух или более столбцов, строящих группы, мы можем исключить операцию MAKE_SET. Конструкция точно такая же. Это работает для меня по мере необходимости. Большое спасибо Pinal Dave за его четкую демонстрацию.


Я не вижу простого ответа, охватывающего часть" раздел по", Поэтому вот мой:

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
  • предложение ORDER BY должно отражать вашу потребность в ROW_NUMBER. Таким образом, уже есть четкое ограничение: вы не можете иметь несколько ROW_NUMBER "эмуляции" этой формы одновременно.
  • порядок "вычисляемого столбца"вопросы. Если у вас есть mysql, вычислите этот столбец в другом порядке, это может не сработать.
  • в этом простом примере Я поставил только один, но вы можете иметь несколько" разделов по " частям

        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
    

Это также может быть решением:

SET @row_number = 0;

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

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;

эта работа идеально подходит для меня, чтобы создать 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

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