ROW_NUMBER () en MySQL

281

¿Hay una buena manera en MySQL para replicar la función de SQL Server ROW_NUMBER()?

Por ejemplo:

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

Entonces podría, por ejemplo, agregar una condición para limitar intRowa 1 para obtener una sola fila con la más alta col3para cada (col1, col2)par.

Pablo
fuente
9
Etiquetado con greatest-n-per-grouppara guiarlo a preguntas similares.
Bill Karwin el
1
Se eliminó la Sql-Serveretiqueta, ya que este fue el elemento más votado en la búsqueda combinada de etiquetas, pero no es realmente relevante para SQL Server.
Martin Smith
para una función simple de número de fila mysql, consulte datamakessense.com/mysql-rownum-row-number-function
AdrianBR
Para MySQL, las únicas soluciones eficientes se analizan en mysql.rjweb.org/doc.php/groupwise_max . Muchas soluciones publicitadas requieren un escaneo completo de la tabla (o peor).
Rick James
2
Solución descaradamente auto promocional para la función analítica real ROW_NUMBER, RANK, DESNSE_RANK en MySQL
Kenneth Xu

Respuestas:

102

Quiero la fila con el col3 más alto individual para cada par (col1, col2).

Es un máximo grupal , una de las preguntas SQL más frecuentes (ya que parece que debería ser fácil, pero en realidad no lo es).

A menudo me vuelvo loco por una unión nula:

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;

"Obtenga las filas en la tabla para las cuales ninguna otra fila con col1 coincidente, col2 tiene una col3 más alta". (Notará que esta y la mayoría de las otras soluciones grupales máximas devolverán varias filas si más de una fila tiene el mismo col1, col2, col3. Si ese es un problema, es posible que necesite algo de procesamiento posterior).

bobince
fuente
2
Pero, ¿qué pasa si hay dos valores máximos de col3 para un par (col1, col2)? Terminarías con dos filas.
Paul
@Paul: si! Acabo de agregar una nota sobre eso en la respuesta hace un tic. Por lo general, puede soltar fácilmente filas adicionales no deseadas en la capa de aplicación posteriormente de forma aleatoria, pero si tiene muchas filas, todas con el mismo col3, puede ser problemático.
bobince
1
Bobince, la solución se hizo bastante popular aquí en SO, pero tengo una pregunta. La solución es básicamente la misma que si alguien intentara encontrar la identificación más grande con la siguiente consulta: SELECT t1.id FROM test t1 LEFT JOIN test t2 ON t1.id>t2.id WHERE t2.id IS NULL;¿No requiere n*n/2 + n/2comparaciones IS NULL para encontrar la fila única? ¿Hay alguna optimización que no veo? Traté de hacerle una pregunta similar a Bill en otro hilo, pero parece haberlo ignorado.
nuevo el
2
@Paul: para abordar el caso en el que existen varias filas que coinciden con el máximo por grupo y desea obtener solo una, siempre puede agregar la clave principal en la lógica de la cláusula ON para romper el empate ... SELECCIONE t0.col3 DE la tabla AS t0 LEFT JOIN tabla AS t1 ON t0.col1 = t1.col1 AND t0.col2 = t1.col2 AND (t1.col3, t1.pk)> (t0.col3, t0.pk) DONDE t1.col1 ES NULO;
Jon Armstrong - Xgc
2
Esto sería más legible comoSELECT 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

No hay funcionalidad de clasificación en MySQL. Lo más cercano que puede obtener es usar una variable:

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

Entonces, ¿cómo funcionaría eso en mi caso? ¿Necesitaría dos variables, una para cada col1 y col2? Col2 necesitaría reiniciarse de alguna manera cuando col1 cambió ...?

Si. Si se tratara de Oracle, podría usar la función LEAD para alcanzar el pico en el siguiente valor. Afortunadamente, Quassnoi cubre la lógica de lo que necesita implementar en MySQL .

Ponis OMG
fuente
1
Hmm ... entonces, ¿cómo funcionaría eso en mi caso? ¿Necesitaría dos variables, una para cada col1 y col2? Col2 necesitaría reiniciarse de alguna manera cuando col1 cambió ...?
Paul
Gracias ... como dije anteriormente, esta respuesta es igualmente aceptada por Bobince, pero solo puedo marcar una :-)
Paul
9
La asignación y lectura de variables definidas por el usuario en la misma declaración no es confiable. esto se documenta aquí: dev.mysql.com/doc/refman/5.0/en/user-variables.html : "Como regla general, nunca debe asignar un valor a una variable de usuario y leer el valor dentro de la misma declaración. Puede obtener los resultados que espera, pero esto no está garantizado. El orden de evaluación para las expresiones que involucran variables de usuario no está definido y puede cambiar en función de los elementos contenidos en una declaración dada ".
Roland Bouman el
1
@Roland: solo he probado en pequeños conjuntos de datos, no he tenido ningún problema. Lástima que MySQL aún no ha abordado la funcionalidad: la solicitud ha estado en funcionamiento desde 2008
OMG Ponies
2
Este parece ser un comportamiento indefinido, como señala Roland. por ejemplo, esto da resultados totalmente incorrectos para una tabla que probé: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

Siempre termino siguiendo este patrón. Dada esta tabla:

+------+------+
|    i |    j |
+------+------+
|    1 |   11 |
|    1 |   12 |
|    1 |   13 |
|    2 |   21 |
|    2 |   22 |
|    2 |   23 |
|    3 |   31 |
|    3 |   32 |
|    3 |   33 |
|    4 |   14 |
+------+------+

Puedes obtener este resultado:

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

Al ejecutar esta consulta, que no necesita ninguna variable definida:

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

¡Espero que ayude!

Mosty Mostacho
fuente
1
si las columnas son VARCHAR o CHAR, ¿cómo puede manejar eso con esta estructura?
Tushar
3
Eres increíble Mosty, estoy buscando exactamente esto
luckykrrish
Acabo de dar esta respuesta usando su lógica para row_number. Gracias.
Utsav
@Tushar los operadores <, >, <=, >=CHAR mango y tipos de datos VARCHAR en orden alfabético; Espero, es exactamente lo que estás buscando.
alex
1
@AlmazVildanov deberías poder usar esta consulta simplemente como una subconsulta para filtrar row_numbers <= 2 y muchas gracias por esta respuesta Mosty, ¡es perfecto!
Zax
61
SELECT 
    @i:=@i+1 AS iterator, 
    t.*
FROM 
    tablename AS t,
    (SELECT @i:=0) AS foo
Peter Johnson
fuente
1
El primero: = parece faltar en la respuesta de @OMG Ponies. Gracias por publicar esto Peter Johnson.
sholsinger
Supongo que (SELECT @i: = 0) AS foo debería ser la primera tabla en la declaración FROM, especialmente si otras tablas usan sub-selecciones
y el
¿Por qué necesitas el '... como foo'?
Tom Chiverton
@TomChiverton Si falta, obtienes: "Código de error: 1248. Cada tabla derivada debe tener su propio alias"
ExStackChanger
1
La asignación de rango aquí está completamente indefinida y esto ni siquiera responde la pregunta
jberryman
27

Consulte este artículo, muestra cómo imitar SQL ROW_NUMBER () con una partición en MySQL. Me encontré con este mismo escenario en una implementación de WordPress. Necesitaba ROW_NUMBER () y no estaba allí.

http://www.explodybits.com/2011/11/mysql-row-number/

El ejemplo en el artículo está usando una sola partición por campo. Para particionar por campos adicionales, puede hacer algo como esto:

  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 

El uso de concat_ws maneja los valores nulos. Probé esto en 3 campos usando int, date y varchar. Espero que esto ayude. Consulte el artículo, ya que desglosa esta consulta y la explica.

abedul
fuente
1
Increíble. Esto realmente hace la partición. Muy útil
Stuart Watt
1
En comparación con la unión automática, esto es mucho más eficiente, pero hay un problema con la lógica, el orden debe ocurrir antes de calcular row_num, concat tampoco es necesario. `` `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 FROM (SELECT * FROM table1 ORDER BY col1, col2, col3) t, (SELECT @row_num: = 1, @ prev_col1: = '', @ prev_col2: = '') var `` `
Kenneth Xu
Si necesita poner esto en una subconsulta, luego agregue limit 18446744073709551615a la order bycláusula force .
xmedeko
concat_wscon la cadena vacía ''es peligroso: concat_ws('',12,3) = concat_ws('',1,23). Es mejor usar un separador '_'o usar la solución @Kenneth Xu.
xmedeko
el enlace de op está muerto; archivo de enlace aquí
usuario2426679
25

Desde MySQL 8.0.0y arriba, puede usar de forma nativa funciones con ventana.

1.4 Qué hay de nuevo en MySQL 8.0 :

Funciones de ventana.

MySQL ahora admite funciones de ventana que, para cada fila de una consulta, realizan un cálculo utilizando filas relacionadas con esa fila. Estos incluyen funciones como RANK (), LAG () y NTILE (). Además, varias funciones agregadas existentes ahora se pueden utilizar como funciones de ventana; por ejemplo, SUM () y AVG ().

ROW_NUMBER () over_clause :

Devuelve el número de la fila actual dentro de su partición. Los números de filas oscilan entre 1 y el número de filas de partición.

ORDER BY afecta el orden en que se numeran las filas. Sin ORDER BY, la numeración de filas es indeterminada.

Manifestación:

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

Lukasz Szozda
fuente
1
suspiro ... por fin!
Used_By_Already
15

También votaría por la solución de Mosty Mostacho con modificaciones menores en su código de consulta:

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

Lo que dará el mismo resultado:

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

para la mesa:

+------+------+
|    i |    j |
+------+------+
|    1 |   11 |
|    1 |   12 |
|    1 |   13 |
|    2 |   21 |
|    2 |   22 |
|    2 |   23 |
|    3 |   31 |
|    3 |   32 |
|    3 |   33 |
|    4 |   14 |
+------+------+

Con la única diferencia de que la consulta no usa JOIN y GROUP BY, se basa en select anidado en su lugar.

abcdn
fuente
¿Se supone que esto es mejor? Parece probable que ambos sean cuadráticos, pero no estoy seguro de cómo interpretar la salida EXPLAIN
jberryman
De hecho, se sabe que las selecciones anidadas no están muy bien optimizadas en MySQL, por lo que esta respuesta es solo para demostrar una técnica de consulta. Los ejemplos basados ​​en variables anteriores funcionan mejor para la mayoría de los casos prácticos, supongo.
abcdn
1
No estoy convencido de cualquiera de las variables respuestas basadas en realidad están usando comportamiento definido ...
jberryman
Lo siento, no estoy seguro de haber entendido lo que quieres decir con "comportamiento definido". ¿Quiere decir que no funciona para usted, o simplemente le preocupa que no esté documentado?
abcdn
1
"Comportamiento indefinido" significa que no está documentado para trabajar y / o documentado para no garantizar que funcione. Consulte las citas y enlaces de documentación en los comentarios de esta página. Se puede devolver lo que uno (modo atacado) quiere / conjeturas / hipotetiza / fantasizes. Para ciertas versiones de la implementación, los programadores de Percona han demostrado que ciertas expresiones de consulta que usan CASE incremental y el uso de variables funcionan al mirar el código. Eso podría cambiar con cualquier lanzamiento.
philipxy
12

Yo definiría una función:

delimiter $$
DROP FUNCTION IF EXISTS `getFakeId`$$
CREATE FUNCTION `getFakeId`() RETURNS int(11)
    DETERMINISTIC
begin
return if(@fakeId, @fakeId:=@fakeId+1, @fakeId:=1);
end$$

entonces podría hacer:

select getFakeId() as id, t.* from table t, (select @fakeId:=0) as t2;

Ahora no tiene una subconsulta, que no puede tener en las vistas.

Quincy
fuente
Funciona con una limitación: si ejecuta la consulta varias veces, obtendrá fakeIds cada vez mayores para el mismo conjunto de resultados
Stephan Richter
podrías enviar set @fakeId = 0; cada vez que desea ejecutar la consulta, no es óptima pero funciona
jmpeace
Un problema realmente extraño sucede si elimina DETERMINISTIC. Entonces el fakeId es incorrecto cuando se utiliza ordenar por. ¿Por qué es esto?
Chris Muench
8

consulta por row_number en mysql

set @row_number=0;
select (@row_number := @row_number +1) as num,id,name from sbs
usuario5528503
fuente
¿Esto se puede usar en consultas de ACTUALIZACIÓN? Estoy intentando pero aparece el error "datos truncados para columna ...".
Diego
1
Si alguien está interesado en usarlo en ACTUALIZACIÓN, debe usarse como una subconsulta para funcionar. ACTUALIZAR <table> SET <field> = (SELECT \ @row_number: = \ @row_number +1) ORDER BY <su columna de pedido>; La columna de orden determina el orden de valor de las filas.
Diego
8

No existe una función como rownum, row_num()en MySQL, pero la forma de evitarlo es la siguiente:

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

La solución que mejor funcionó fue usar una subconsulta como esta:

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

Las columnas PARTITION BY simplemente se comparan con '=' y se separan con AND. Las columnas ORDER BY se compararían con '<' o '>' y se separarían por OR.

He encontrado que esto es muy flexible, incluso si es un poco costoso.

snydergd
fuente
4

La funcionalidad de rownumber no se puede imitar. Es posible que obtenga los resultados que espera, pero lo más probable es que se decepcione en algún momento. Esto es lo que dice la documentación de mysql:

Para otras declaraciones, como SELECT, puede obtener los resultados que espera, pero esto no está garantizado. En la siguiente declaración, puede pensar que MySQL evaluará @a primero y luego realizará una asignación en segundo lugar: SELECCIONE @a, @a: = @ a + 1, ...; Sin embargo, el orden de evaluación para las expresiones que involucran variables de usuario no está definido.

Saludos, Georgi.

usuario3503199
fuente
No te sigo. ¿Cómo es que "@i: = @i + 1 como posición" no es un reemplazo directo para "ROW_NUMBER () over (ordenar por suma (puntaje) desc) como posición"?
Tom Chiverton
1
@TomChiverton Porque su comportamiento no está definido, como dice el manual allí mismo.
philipxy
4

MariaDB 10.2 está implementando "Funciones de ventana", incluyendo RANK (), ROW_NUMBER () y varias otras cosas:

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

Según una charla en Percona Live este mes, están razonablemente bien optimizados.

La sintaxis es idéntica al código en la pregunta.

Rick James
fuente
2

No veo ninguna respuesta simple que cubra la parte "PARTICIÓN POR", así que aquí está la mía:

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
  • La cláusula ORDER BY debe reflejar su necesidad de ROW_NUMBER. Por lo tanto, ya existe una clara limitación: no puede tener varias ROW_NUMBER "emulación" de este formulario al mismo tiempo.
  • El orden de la "columna calculada" es importante . Si tiene que mysql calcule esas columnas en otro orden, es posible que no funcione.
  • En este ejemplo simple, solo pongo uno pero puedes tener varias partes "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
fuente
1

Un poco tarde pero también puede ayudar a alguien que busca respuestas ...

Ejemplo entre filas / número de fila: consulta recursiva que se puede usar en cualquier 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
Arte
fuente
2
Lo siento, pero que yo sepa, MySQL no admite expresiones de tabla comunes .
Álvaro González
lo hace ahora ... @ ÁlvaroGonzález MySQL 8 solo admite CTE y funciones de ventana, por lo que esta respuesta realmente no tiene sentido usarla en versiones anteriores de MySQL ...
Raymond Nijland
1

Esto permite lograr la misma funcionalidad que ROW_NUMBER () Y PARTITION BY en 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
fuente
1

También un poco tarde, pero hoy tenía la misma necesidad, así que busqué en Google y finalmente encontré un enfoque general simple aquí en el artículo de Pinal Dave http://blog.sqlauthority.com/2014/03/09/mysql-reset-row -número-para-cada-grupo-partición-por-fila-número /

Quería centrarme en la pregunta original de Paul (ese también era mi problema), así que resumo mi solución como un ejemplo de trabajo.

Debido a que queremos particionar en dos columnas, crearía una variable SET durante la iteración para identificar si se inició un nuevo grupo.

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

El 3 significa en el primer parámetro de MAKE_SET que quiero ambos valores en el SET (3 = 1 | 2). Por supuesto, si no tenemos dos o más columnas para construir los grupos, podemos eliminar la operación MAKE_SET. La construcción es exactamente la misma. Esto está funcionando para mí según sea necesario. Muchas gracias al Pinal Dave por su clara demostración.

Miklos Krivan
fuente
1
Tenga ORDER BYen cuenta que en una subconsulta podría ignorarse (consulte mariadb.com/kb/en/mariadb/… ). La solución sugerida para eso es agregar LIMIT 18446744073709551615a la subconsulta, lo que obliga a una especie. Sin embargo, esto podría causar problemas de rendimiento y no es válido para tablas realmente enormes :)
pnomolos
1

Esto también podría ser una solución:

SET @row_number = 0;

SELECT 
    (@row_number:=@row_number + 1) AS num, firstName, lastName
FROM
    employees
Rishabh Pandey
fuente
Sin embargo, no hace ninguna partición, y no es significativamente diferente a una respuesta más citada
Caius Jard
1

MySQL ha admitido ROW_NUMBER () desde la versión 8.0+ .

Si usa MySQL 8.0 o posterior, échele un vistazo a la función ROW_NUMBER (). De lo contrario, debe emular la función ROW_NUMBER ().

Row_number () es una función de clasificación que devuelve un número secuencial de una fila, comenzando desde 1 para la primera fila.

para la versión anterior,

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

Importante: considere actualizar a MySQL 8+ y use la función definida y documentada ROW_NUMBER (), y elimine los viejos hacks vinculados a una versión antigua limitada de características de MySQL

Ahora aquí está uno de esos hacks:

Las respuestas aquí que usan variables de consulta en su mayoría / todas parecen ignorar el hecho de que la documentación dice (parafraseando):

No confíe en los elementos de la lista SELECT que se evalúan en orden de arriba a abajo. No asigne variables en un elemento SELECT y no las use en otro.

Como tal, existe el riesgo de que generen la respuesta incorrecta, porque generalmente hacen un

select
  (row number variable that uses partition variable),
  (assign partition variable)

Si alguna vez se evalúan de abajo hacia arriba, el número de fila dejará de funcionar (sin particiones)

Por lo tanto, debemos usar algo con un orden de ejecución garantizado. Ingrese CASO CUANDO:

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

Como se describe en ld, el orden de asignación de prevcol es importante: se debe comparar prevcol con el valor de la fila actual antes de asignarle un valor de la fila actual (de lo contrario, sería el valor col de las filas actuales, no el valor col de la fila anterior) .

Así es como encaja esto:

  • El primero CUANDO se evalúa. Si la columna de esta fila es igual a la columna de la fila anterior, entonces @r se incrementa y se devuelve desde CASE. Estos valores de retorno de led se almacenan en @r. Es una característica de MySQL que la asignación devuelve el nuevo valor de lo que se asigna a @r en las filas de resultados.

  • Para la primera fila del conjunto de resultados, @prevcol es nulo (se inicializa como nulo en la subconsulta), por lo que este predicado es falso. Este primer predicado también devuelve falso cada vez que cambia la columna (la fila actual es diferente a la fila anterior). Esto hace que el segundo CUANDO sea evaluado.

  • El segundo predicado WHEN siempre es falso, y existe únicamente para asignar un nuevo valor a @prevcol. Debido a que la columna de esta fila es diferente de la columna de la fila anterior (sabemos esto porque si fuera lo mismo, el primer CUANDO se hubiera utilizado), tenemos que asignar el nuevo valor para mantenerlo para probar la próxima vez. Debido a que la asignación se realiza y luego el resultado de la asignación se compara con nulo, y todo lo que se equipare con nulo es falso, este predicado siempre es falso. Pero al menos evaluarlo hizo su trabajo al mantener el valor de col de esta fila, por lo que puede evaluarse contra el valor de col de la siguiente fila

  • Debido a que el segundo CUANDO es falso, significa que en situaciones donde la columna por la que estamos particionando (col) ha cambiado, es ELSE el que da un nuevo valor para @r, reiniciando la numeración desde 1

Llegamos a una situación en la que esto:

SELECT
  t.*, 
  ROW_NUMBER() OVER(PARTITION BY pcol1, pcol2, ... pcolX ORDER BY ocol1, ocol2, ... ocolX) rn
FROM
  t

Tiene la forma general:

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

Notas al pie:

  • La p en pcol significa "partición", la o en ocol significa "orden" - en la forma general eliminé la "anterior" del nombre de la variable para reducir el desorden visual

  • Los corchetes (@pcolX := colX) = nullson importantes. Sin ellos asignarás nulo a @pcolX y las cosas dejarán de funcionar

  • Es un compromiso que el conjunto de resultados también debe ser ordenado por las columnas de partición, para que la columna anterior se pueda comparar. Por lo tanto, no puede ordenar su número de rown de acuerdo con una columna, pero su conjunto de resultados se ordenó a otro. Es posible que pueda resolver esto con subconsultas, pero creo que los documentos también indican que el pedido de subconsultas puede ignorarse a menos que se use LIMIT y esto podría afectar actuación

  • No he profundizado en ello más allá de probar que el método funciona, pero si existe el riesgo de que los predicados en el segundo CUANDO se optimicen (cualquier cosa en comparación con nulo es nulo / falso, ¿por qué molestarse en ejecutar la asignación?) Y no se ejecuta , también se detiene. Esto no parece suceder en mi experiencia, pero con mucho gusto aceptaré comentarios y propondré una solución si pudiera ocurrir razonablemente.

  • Puede ser conveniente convertir los valores nulos que crean @pcolX a los tipos reales de sus columnas, en la subconsulta que crea las variables @pcolX, a saber: select @pcol1 := CAST(null as INT), @pcol2 := CAST(null as DATE)

Caius Jard
fuente
No hay justificación para esto. Al igual que las otras respuestas que asignan y leen desde la misma variable.
philipxy
¿Puede proporcionar más detalles phil?
Caius Jard
Vea mis otros comentarios en esta página. Google 'sitio: stackoverflow.com "philipxy" variable mysql (establecer O asignar O asignar O escribir) leer': Una respuesta por mí y un informe de error vinculado en un comentario mío en esta pregunta donde la respuesta aceptada cita el manual pero de inmediato en reclamos está bien hacer algo en contradicción. Lea el manual re variables y re asignación.
philipxy
Entiendo tu preocupación
Caius Jard
0

Esta no es la solución más sólida, pero si solo está buscando crear un rango particionado en un campo con solo unos pocos valores diferentes, puede que no sea difícil usar algún caso cuando la lógica con tantas variables como necesite.

Algo como esto me ha funcionado en el pasado:

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

Espero que tenga sentido / ayuda!

bibzzzz
fuente
-1

Esto funciona perfectamente para mí para crear RowNumber cuando tenemos más de una columna. En este caso dos columnas.

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
fuente
-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;
usuario4605644
fuente
1
Intente formatear las respuestas y proporcione un contexto adicional sobre lo que está tratando de hacer. Por el momento no es más que texto mal formateado.
Yannick Meeus
2
Esto no parece tener ninguna relación con la pregunta original. Si tiene alguna pregunta, pregúntela por separado.
Jeroen Mostert
-5
SELECT 
    col1, col2, 
    count(*) as intRow
FROM Table1
GROUP BY col1,col2
ORDER BY col3 desc
Nickson Nyabote
fuente