ROW_NUMBER () в MySQL

281

Есть ли хороший способ в MySQL для репликации функции SQL Server ROW_NUMBER()?

Например:

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

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

Павел
источник
9
Помечено, greatest-n-per-groupчтобы вести вас к подобным вопросам.
Билл Карвин
1
Удален Sql-Serverтег, так как это был самый высокий голосующий элемент в комбинированном поиске тегов, но он не имеет отношения к SQL Server.
Мартин Смит
для простой функции нумерации строк mysql, проверьте datamakessense.com/mysql-rownum-row-number-function
AdrianBR
Для MySQL единственные эффективные решения обсуждаются в mysql.rjweb.org/doc.php/groupwise_max . Многие опубликованные решения требуют полного сканирования таблицы (или хуже).
Рик Джеймс
2
Бесстыдное саморекламирующееся решение для реальной аналитической функции ROW_NUMBER, RANK, DESNSE_RANK в MySQL
Кеннет Сюй

Ответы:

102

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

Это групповой максимум , один из наиболее часто задаваемых вопросов SQL (поскольку кажется, что это должно быть легко, но на самом деле это не так).

Я часто пухленький для нулевого самостоятельного соединения:

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. Если это проблема, вам может потребоваться некоторая постобработка.)

bobince
источник
2
Но что, если есть два максимальных значения col3 для пары (col1, col2)? Вы бы в конечном итоге с двумя рядами.
Пол
@ Пол: да! Просто добавил примечание об этом в ответ тик назад. Обычно вы можете легко удалить ненужные дополнительные строки на уровне приложения впоследствии на некоторой случайной основе, но если у вас много строк с одинаковым col3, это может быть проблематично.
bobince
1
bobince, решение стало довольно популярным здесь, на SO, но у меня есть вопрос. Решение в основном такое же, как если бы кто-то попытался найти самый большой идентификатор с помощью следующего запроса: SELECT t1.id FROM test t1 LEFT JOIN test t2 ON t1.id>t2.id WHERE t2.id IS NULL;не требует ли n*n/2 + n/2сравнение IS NULL для поиска одной строки? Происходят ли какие-либо оптимизации, которых я не вижу? Я пытался задать аналогичный вопрос Биллу в другой ветке, но он, похоже, проигнорировал его.
newtover
2
@Paul - для решения ситуации, когда существует несколько строк, соответствующих максимальному числу на группу, и вы хотите получить только одну, вы всегда можете добавить первичный ключ в логику предложения ON, чтобы разорвать связь ... SELECT t0.col3 FROM таблица КАК t0 ЛЕВАЯ ОБЪЕДИНЕННАЯ таблица КАК t1 ВКЛ t0.col1 = t1.col1 И t0.col2 = t1.col2 И (t1.col3, t1.pk)> (t0.col3, t0.pk) ГДЕ t1.col1 НЕДЕЙСТВИТЕЛЕН;
Джон Армстронг - Xgc
2
Это было бы более читабельно, какSELECT t0.col3 FROM table AS t0 WHERE NOT EXISTS (select 1 from table AS t1 ON t0.col1=t1.col1 AND t0.col2=t1.col2 AND t1.col3>t0.col3)
wrschneider
204

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

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

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

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

OMG пони
источник
1
Хм .... так как это будет работать в моем случае? Мне нужны две переменные, по одной для каждого из col1 и col2? Col2 нужно как-то сбросить, когда col1 изменился ..?
Пол
Спасибо ... как я уже сказал выше, этот ответ одинаково принят Бобинсом, но я могу отметить только один :-)
Пол
9
Назначение и чтение пользовательских переменных в одном и том же утверждении не является надежным. это задокументировано здесь: dev.mysql.com/doc/refman/5.0/en/user-variables.html : «Как правило, вы никогда не должны присваивать значение пользовательской переменной и читать значение в пределах одного и того же оператора. Вы можете получить ожидаемые результаты, но это не гарантировано. Порядок вычисления выражений с участием пользовательских переменных не определен и может изменяться в зависимости от элементов, содержащихся в данном утверждении. "
Ролан Буман
1
@Roland: я тестировал только на небольших наборах данных, не было никаких проблем. Жаль, что MySQL еще не рассмотрел функциональность - запрос поступил с 2008 года
OMG Ponies
2
Это, кажется, неопределенное поведение, как отмечает Роланд. например, это дает совершенно неверные результаты для таблицы, которую я пробовал:SELECT @row_num:=@row_num+1 AS row_number, t.id FROM (SELECT * FROM table1 WHERE col = 264 ORDER BY id) t, (SELECT @row_num:=0) var;
jberryman
81

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

+------+------+
|    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
Ты потрясающий Мости, я ищу именно это
luckykrrish
Просто дал этот ответ, используя вашу логику для row_number. Спасибо.
Утсав
@Tushar операторов <, >, <=, >=ручка CHAR и типы данных VARCHAR на алфавитном порядке; Я ожидаю, это именно то, что вы ищете.
Алекс
1
@AlmazVildanov, вы должны быть в состоянии использовать этот запрос просто как подзапрос для фильтрации. row_numbers <= 2 Огромное спасибо за этот ответ, Мости, это прекрасно!
Закс
61
SELECT 
    @i:=@i+1 AS iterator, 
    t.*
FROM 
    tablename AS t,
    (SELECT @i:=0) AS foo
Питер Джонсон
источник
1
Первое: =, кажется, отсутствует в ответе @OMG Ponies. Спасибо за публикацию этого Питера Джонсона.
sholsinger
Я думаю (SELECT @i: = 0) AS Foo должна быть первой таблицы в FROM заявлении, особенно если другие таблицы используют суб-выбирает
andig
Зачем вам вообще нужен «.. как фу»?
Том Чивертон,
@TomChiverton Если он отсутствует, вы получите: «Код ошибки: 1248. Каждая производная таблица должна иметь свой собственный псевдоним»
ExStackChanger
1
Присвоение звания здесь совершенно не определено, и это даже не отвечает на вопрос
jberryman
27

Посмотрите эту статью, в ней показано, как имитировать 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 обрабатывает нуль. Я проверил это на 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 `` `
Кеннет Сюй
Если вам нужно поместить это в подзапрос, то добавьте limit 18446744073709551615к order byпредложению force .
xmedeko
concat_wsс пустой строкой ''опасно concat_ws('',12,3) = concat_ws('',1,23). Лучше использовать какой-то разделитель '_'или использовать решение @Kenneth Xu.
xmedeko
ссылка оп мертвая; архив ссылки здесь
user2426679
25

Кроме того, 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
вздох ... наконец-то!
Used_By_Already
15

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

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

abcdn
источник
Это должно быть лучше? Похоже, они оба являются квадратичными, но я не уверен, как интерпретировать вывод EXPLAIN
jberryman
На самом деле, вложенные селекторы, как известно, не очень хорошо оптимизированы в MySQL, поэтому этот ответ только для демонстрации техники запросов. Я полагаю, приведенные выше примеры на основе переменных работают лучше для большинства практических случаев
abcdn
1
Я не уверен, что ответы на основе переменных на самом деле используют определенное поведение ...
jberryman
Извините, я не уверен, что понял, что вы имели в виду под «определенным поведением». Вы имеете в виду, что это не работает для вас, или вы просто обеспокоены тем, что это не задокументировано?
abcdn
1
«Неопределенное поведение» означает, что оно не документировано для работы и / или не документировано для гарантированной работы. Смотрите документацию цитаты и ссылки в комментариях на этой странице. Это может вернуть то, что (необоснованно) хочет / угадывает / гипотезирует / фантазирует. Для определенных версий реализации было показано, что определенные выражения запросов, использующие приращение CASE и использующие переменные, работают программистами в Percona, просматривая код. Это может измениться с любым выпуском.
Филипп
12

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

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;

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

Quincy
источник
Работает с одним ограничением: если вы выполните запрос несколько раз, вы получите постоянно увеличивающиеся fakeIds для одного и того же набора результатов
Стефан Рихтер
вы можете отправить set @fakeId = 0; каждый раз, когда вы хотите выполнить запрос, не оптимально, но работает
jmpeace
Действительно странная проблема возникает, если вы удалите DETERMINISTIC. Тогда fakeId неверен при использовании order by. Почему это?
Крис Мюнх
8

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

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

В MySQL нет такой функции rownum, row_num()как наоборот, как показано ниже:

select 
      @s:=@s+1 serial_no, 
      tbl.* 
from my_table tbl, (select @s:=0) as s;
Мд. Камруззаман
источник
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.

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

snydergd
источник
4

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

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

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

user3503199
источник
Я не следую Как "@i: = @i + 1 как позиция" не является прямой заменой для "ROW_NUMBER () over (порядок по сумме (оценка) desc) как позиция"?
Том Чивертон,
1
@ TomChiverton Потому что его поведение не определено, как говорится в руководстве прямо здесь.
Филиппий
4

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

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

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

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

Рик Джеймс
источник
2

Я не вижу простого ответа, охватывающего часть "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
Serge Profafilecebook
источник
1

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

Между row / 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
Искусство
источник
2
Извините, но, насколько мне известно, MySQL не поддерживает общие табличные выражения .
Альваро Гонсалес,
это происходит сейчас ... @ ÁlvaroGonzález MySQL 8 поддерживает только CTE и оконные функции, поэтому этот ответ не имеет смысла использовать в более старых версиях MySQL ..
Raymond Nijland
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
Alankar
источник
1

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

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

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

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

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

SET @row_number = 0;

SELECT 
    (@row_number:=@row_number + 1) AS num, firstName, lastName
FROM
    employees
Ришабх Пандей
источник
Это не делает никакого разделения, хотя, и это существенно не отличается от более цитируемого ответа
Caius Jard
1

MySQL поддерживает ROW_NUMBER () начиная с версии 8.0+ .

Если вы используете MySQL 8.0 или более позднюю версию, проверьте функцию ROW_NUMBER (). В противном случае вы должны эмулировать функцию ROW_NUMBER ().

Row_number () - это функция ранжирования, которая возвращает порядковый номер строки, начиная с 1 для первой строки.

для старой версии,

SELECT t.*, 
       @rowid := @rowid + 1 AS ROWID
  FROM TABLE t, 
       (SELECT @rowid := 0) dummy;
Мохидин бин Мухаммед
источник
1

Важное замечание: Пожалуйста, рассмотрите возможность обновления до 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

Как и в общих чертах, порядок назначения prevcol важен - его нужно сравнить со значением текущей строки, прежде чем мы присвоим ему значение из текущей строки (в противном случае это будет значение столбца текущей строки, а не значение столбца предыдущей строки) ,

Вот как это сочетается:

  • Первый КОГДА оценивается. Если col этой строки совпадает с col предыдущей строки, то @r увеличивается и возвращается из CASE. Эти возвращаемые светодиодные значения хранятся в @r. Особенностью MySQL является то, что присваивание возвращает новое значение того, что назначено в @r, в строки результатов.

  • Для первой строки в наборе результатов @prevcol имеет значение null (в подзапросе оно инициализируется значением null), поэтому этот предикат имеет значение false. Этот первый предикат также возвращает false при каждом изменении col (текущая строка отличается от предыдущей строки). Это заставляет второй КОГДА быть оцененным.

  • Второй предикат WHEN всегда ложен, и он существует исключительно для назначения нового значения @prevcol. Поскольку col этой строки отличается от col предыдущей строки (мы знаем это потому, что если бы это было то же самое, использовался бы первый WHEN), мы должны присвоить новое значение, чтобы сохранить его для тестирования в следующий раз. Поскольку присваивание выполняется, а затем результат присваивания сравнивается с нулем, а все, что приравнивается к нулю, является ложным, этот предикат всегда ложен. Но, по крайней мере, оценив его, он сохранил значение col из этой строки, чтобы его можно было сравнить со значением col следующей строки.

  • Поскольку второе WHEN имеет значение false, это означает, что в ситуациях, когда столбец, который мы разделяем (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

Примечания:

  • P в pcol означает «раздел», o в ocol означает «порядок» - в общем виде я убрал «prev» из имени переменной, чтобы уменьшить визуальный беспорядок

  • Скобки вокруг (@pcolX := colX) = nullважны. Без них вы присваиваете @pcolX значение null, и все перестает работать

  • Это компромисс, что результирующий набор должен быть упорядочен также по столбцам разделов, чтобы сравнение предыдущего столбца сработало. Таким образом, вы не можете упорядочить свое числовое число в соответствии с одним столбцом, но ваш набор результатов должен быть упорядочен в другом. Возможно, вы сможете решить эту проблему с помощью подзапросов, но я считаю, что в документах также говорится, что упорядочение подзапросов может игнорироваться, если не используется LIMIT, и это может повлиять производительность

  • Я не вдавался в подробности после тестирования того, что метод работает, но если есть риск, что предикаты во втором случае будут оптимизированы (что-либо по сравнению с нулем равно null / false, так зачем беспокоиться о выполнении назначения) и не выполняется также останавливается. По моему опыту, этого не происходит, но я с удовольствием приму комментарии и предложу решение, если это может произойти.

  • Может быть целесообразно привести нулевые значения, которые создают @pcolX, к фактическим типам ваших столбцов в подзапросе, который создает переменные @pcolX, а именно: select @pcol1 := CAST(null as INT), @pcol2 := CAST(null as DATE)

Caius Jard
источник
Там нет никаких оснований для этого. Так же, как и другие ответы, которые назначают и читают из той же переменной.
Philipxy
Можете ли вы предоставить более подробно Фил?
Caius Jard
Смотрите мои другие комментарии на этой странице. Googling 'site: stackoverflow.com "philipxy" переменная mysql (установить ИЛИ назначить ИЛИ присвоить ИЛИ написать) читать': ответ от меня и сообщение об ошибке, связанное мной в комментарии на этот вопрос, где принятый ответ цитирует руководство еще сразу в претензиях нормально делать что-то противоречащее этому. Прочитайте руководство по переменным и повторному назначению.
Филиппа
Я понимаю вашу заботу
Кайус
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>
;

Надеюсь, что это имеет смысл / помогает!

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