Номер строки () в 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 230

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 для достижения пика при следующем значении. К счастью, квасной охватывает логика для того, что вам нужно реализовать в 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 я проверил это против 3 полей, используя int, date и varchar. Надеюсь, это поможет. Ознакомьтесь со статьей, поскольку она разбивает этот запрос и объясняет его.

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

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

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

функциональность 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 в этом месяце, они достаточно хорошо оптимизированы.

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

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

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

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

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

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

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

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

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