mysql sql sql-rank

Determine el rango basado en mĂșltiples columnas en MySQL



sql-rank (4)

Tengo una tabla que tiene 3 campos, quiero clasificar la columna según user_id y game_id.

Aquí está el Fiddle de SQL: http://sqlfiddle.com/#!9/883e9d/1

La mesa ya tengo:

user_id | game_id | game_detial_sum | --------|---------|--------------------| 6 | 10 | 1000 | 6 | 11 | 260 | 7 | 10 | 1200 | 7 | 11 | 500 | 7 | 12 | 360 | 7 | 13 | 50 |

Rendimiento esperado :

user_id | game_id | game_detial_sum | user_game_rank | --------|---------|--------------------|------------------| 6 | 10 | 1000 | 1 | 6 | 11 | 260 | 2 | 7 | 10 | 1200 | 1 | 7 | 11 | 500 | 2 | 7 | 12 | 360 | 3 | 7 | 13 | 50 | 4 |

Mis esfuerzos hasta ahora:

SET @s := 0; SELECT user_id,game_id,game_detail, CASE WHEN user_id = user_id THEN (@s:=@s+1) ELSE @s = 0 END As user_game_rank FROM game_logs

Edición: (De los Comments OP): los pedidos se basan en el orden descendente de game_detail

orden de game_detail


En una Tabla Derivada (subconsulta dentro de la cláusula FROM ), game_detail nuestros datos de modo que todas las filas que tienen los mismos valores de user_id se unan, y la clasificación entre ellas se basa en game_detail en orden Descendente.

Ahora, usamos este conjunto de resultados y usamos expresiones condicionales CASE..WHEN para evaluar la numeración de filas. Será como una técnica de bucle (que utilizamos en el código de la aplicación, por ejemplo, PHP). Almacenaríamos los valores de la fila anterior en las variables definidas por el usuario y luego verificaríamos los valores de la fila actual en comparación con la fila anterior. Eventualmente, le asignaremos el número de fila en consecuencia.

Edición: Basado en los docs MySQL y la observación de @Gordon Linoff:

El orden de evaluación de las expresiones que involucran variables de usuario no está definido. Por ejemplo, no hay garantía de que SELECT @a, @a: = @ a + 1 evalúe @a primero y luego realice la asignación.

Necesitaremos evaluar el número de fila y asignar el valor user_id a @u variable dentro de la misma expresión.

SET @r := 0, @u := 0; SELECT @r := CASE WHEN @u = dt.user_id THEN @r + 1 WHEN @u := dt.user_id /* Notice := instead of = */ THEN 1 END AS user_game_rank, dt.user_id, dt.game_detail, dt.game_id FROM ( SELECT user_id, game_id, game_detail FROM game_logs ORDER BY user_id, game_detail DESC ) AS dt

Resultado

| user_game_rank | user_id | game_detail | game_id | | -------------- | ------- | ----------- | ------- | | 1 | 6 | 260 | 11 | | 2 | 6 | 100 | 10 | | 1 | 7 | 1200 | 10 | | 2 | 7 | 500 | 11 | | 3 | 7 | 260 | 12 | | 4 | 7 | 50 | 13 |

Ver en DB Fiddle

Una nota interesante de MySQL docs , que descubrí recientemente:

Las versiones anteriores de MySQL hicieron posible asignar un valor a una variable de usuario en declaraciones que no sean SET. Esta funcionalidad es compatible con MySQL 8.0 para compatibilidad con versiones anteriores, pero está sujeta a eliminación en una versión futura de MySQL.

Además, gracias a un miembro de SO, encontré este blog del equipo de MySQL: https://mysqlserverteam.com/row-numbering-ranking-how-to-use-less-user-variables-in-mysql-queries/

La observación general es que el uso de ORDER BY con la evaluación de las variables de usuario en el mismo bloque de consulta, no garantiza que los valores sean siempre correctos. Como, el optimizador de MySQL puede entrar en su lugar y cambiar nuestro presunto orden de evaluación.

El mejor enfoque para este problema sería actualizar a MySQL 8+ y utilizar la funcionalidad Row_Number() :

Esquema (MySQL v8.0)

SELECT user_id, game_id, game_detail, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY game_detail DESC) AS user_game_rank FROM game_logs ORDER BY user_id, user_game_rank;

Resultado

| user_id | game_id | game_detail | user_game_rank | | ------- | ------- | ----------- | -------------- | | 6 | 11 | 260 | 1 | | 6 | 10 | 100 | 2 | | 7 | 10 | 1200 | 1 | | 7 | 11 | 500 | 2 | | 7 | 12 | 260 | 3 | | 7 | 13 | 50 | 4 |

Ver en DB Fiddle


La mejor solución en MySQL, antes de la versión 8.0 es la siguiente:

select gl.*, (@rn := if(@lastUserId = user_id, @rn + 1, if(@lastUserId := user_id, 1, 1) ) ) as user_game_rank from (select gl.* from game_logs gl order by gl.user_id, gl.game_detail desc ) gl cross join (select @rn := 0, @lastUserId := 0) params;

El ordenamiento se realiza en una subconsulta. Esto es necesario a partir de MySQL 5.7. Las asignaciones de variables están todas en una expresión, por lo que no importa el orden diferente de evaluación de las expresiones (y MySQL no garantiza el orden de evaluación de las expresiones).


Puede utilizar una sub consulta relacionada muy simple:

SELECT *, ( SELECT COUNT(DISTINCT game_detail) + 1 FROM game_logs AS x WHERE user_id = t.user_id AND game_detail > t.game_detail ) AS user_game_rank FROM game_logs AS t ORDER BY user_id, user_game_rank

DB Fiddle

Es más lento pero mucho más confiable que las variables de usuario. Todo lo que se necesita es una ÚNETE para romperlos.


SELECT user_id, game_id, game_detail, CASE WHEN user_id = @lastUserId THEN @rank := @rank + 1 ELSE @rank := 1 END As user_game_rank, @lastUserId := user_id FROM game_logs cross join (select @rank := 0, @lastUserId := 0) r order by user_id, game_detail desc

Demostración de SQLFiddle