ROW_NUMBER () в MySQL

202

Есть ли хороший способ в 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).

Показать ещё 5 комментариев
Теги:
rank
row-number
greatest-n-per-group

20 ответов

86
Лучший ответ

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

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

  • 1
    Но что, если есть два максимальных значения col3 для пары (col1, col2)? Вы бы в конечном итоге с двумя рядами.
  • 0
    @ Пол: да! Просто добавил примечание об этом в ответ тик назад. Обычно вы можете легко удалить ненужные дополнительные строки на уровне приложения впоследствии на некоторой случайной основе, но если у вас много строк с одинаковым col3, это может быть проблематично.
Показать ещё 11 комментариев
172

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

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

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

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

  • 0
    Хм .... так как это будет работать в моем случае? Мне нужно две переменные, по одной для каждого из col1 и col2? Col2 нужно будет как-то переустанавливать, когда col1 меняется ..?
  • 0
    Спасибо ... как я сказал выше, этот ответ в равной степени принят Бобинсом, но я могу отметить только один :-)
Показать ещё 6 комментариев
67

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

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

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

  • 1
    если столбцы VARCHAR или CHAR, как вы можете справиться с этой структурой?
  • 3
    Ты потрясающий Мости, я ищу именно это
Показать ещё 4 комментария
49
SELECT 
    @i:=@i+1 AS iterator, 
    t.*
FROM 
    tablename AS t,
    (SELECT @i:=0) AS foo
  • 1
    Первое: =, кажется, отсутствует в ответе @OMG Ponies. Спасибо за публикацию этого Питера Джонсона.
  • 0
    Я думаю (SELECT @i: = 0) AS foo должна быть первой таблицей в выражении FROM, особенно если в других таблицах используются подвыборы
Показать ещё 5 комментариев
22

Ознакомьтесь с этой статьей, она показывает, как имитировать 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. Надеюсь это поможет. Просмотрите статью, так как она нарушает этот запрос и объясняет это.

  • 1
    Потрясающие. Это на самом деле делает разделение. Очень кстати
  • 1
    По сравнению с самостоятельным объединением это намного эффективнее, но есть проблема с логикой, порядок должен произойти до вычисления row_num, concat также не требуется. `` `SELECT @row_num: = IF (@ prev_col1 = t.col1 AND @ prev_col2 = t.col2), @ row_num + 1, 1) AS RowNumber, t.col1, t.col2, t.col3, t.col4 , @ prev_col1: = t.col1, @ prev_col2: = t.col2 ОТ (ВЫБРАТЬ * ИЗ таблицы 1 ЗАКАЗАТЬ по col1, col2, col3) t, (ВЫБРАТЬ @row_num: = 1, @ prev_col1: = '', @ prev_col2: = '') var `` `
Показать ещё 4 комментария
12

Я также проголосую за решение 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, вместо этого полагаясь на вложенный выбор.

  • 0
    Это должно быть лучше? Похоже, они оба являются квадратичными, но я не уверен, как интерпретировать вывод EXPLAIN
  • 0
    На самом деле, вложенные селекторы, как известно, не очень хорошо оптимизированы в MySQL, так что этот ответ только для демонстрации техники запросов. Я полагаю, что приведенные выше примеры на основе переменных работают лучше для большинства практических случаев.
Показать ещё 4 комментария
9

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

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;

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

  • 0
    Работает с одним ограничением: если вы выполните запрос несколько раз, вы получите постоянно увеличивающиеся fakeIds для одного и того же набора результатов.
  • 0
    Вы можете отправить set @fakeId = 0; каждый раз, когда вы хотите выполнить запрос, не оптимально, но работает
6

От 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 Demo

  • 1
    вздох ... наконец-то!
  • 0
    @Used_By_Already Да, MySQL становится современным :)
6

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

set @row_number=0;
select (@row_number := @row_number +1) as num,id,name from sbs
  • 0
    Это можно использовать в запросах ОБНОВЛЕНИЯ? Я пытаюсь, но получаю сообщение об ошибке "усечены данные для столбца ...".
  • 0
    Если кто-то заинтересован в использовании его в UPDATE, он должен быть использован как подзапрос для работы. ОБНОВЛЕНИЕ <таблица> SET <поле> = (SELECT \ @row_number: = \ @row_number +1) ORDER BY <столбец вашего заказа>; Столбец порядка определяет порядок расположения строк.
5

В MySQL нет funtion, например rownum, row_num(), но путь примерно такой:

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

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

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

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

  • 0
    Я не следую Как "@i: = @i + 1 в качестве позиции" не является прямой заменой для "ROW_NUMBER () over (порядок по сумме (счету) в качестве позиции")?
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 будут сравниваться с '<' или ' > ', и разделены OR.

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

3

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

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

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

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

1

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

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

Я хотел сосредоточиться на первоначальном вопросе Павла (это тоже моя проблема), поэтому я обобщаю свое решение как рабочий пример.

Beacuse, который мы хотим разделить на два столбца, я бы создал переменную 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. Конструкция точно такая же. Это работает для меня, как требуется. Большое спасибо Пиналу Дейву за его четкую демонстрацию.

  • 0
    Обратите внимание, что ORDER BY в подзапросе может быть проигнорировано (см. Mariadb.com/kb/en/mariadb/… ). Для решения этой проблемы предлагается добавить LIMIT 18446744073709551615 к подзапросу, что LIMIT 18446744073709551615 к сортировке. Однако это может вызвать проблемы с производительностью и не подходит для действительно ужасных огромных таблиц :)
1

Это позволяет использовать те же функции, что и ROW_NUMBER() AND 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
1

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

Между строками /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
0

Эта работа идеально подходит для создания 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
-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;
  • 0
    Пожалуйста, попробуйте отформатировать любые ответы и дать дополнительный контекст относительно того, что вы пытаетесь сделать. На данный момент это не что иное, как плохо отформатированный текст.
  • 2
    Похоже, это не имеет никакого отношения к исходному вопросу. Если у вас есть собственный вопрос, пожалуйста, задавайте его отдельно.
-4
SELECT 
    col1, col2, 
    count(*) as intRow
FROM Table1
GROUP BY col1,col2
ORDER BY col3 desc

Ещё вопросы

Сообщество Overcoder
Наверх
Меню