MySQL obtiene la posición de la fila en ORDER BY

86

Con la siguiente tabla MySQL:

+-----------------------------+
+ id INT UNSIGNED             +
+ name VARCHAR(100)           +
+-----------------------------+

¿Cómo puedo seleccionar una sola fila Y su posición entre las otras filas de la tabla, cuando se ordena por name ASC. Entonces, si los datos de la tabla se ven así, cuando se ordenan por nombre:

+-----------------------------+
+ id | name                   +
+-----------------------------+
+  5 | Alpha                  +
+  7 | Beta                   +
+  3 | Delta                  +
+ .....                       +
+  1 | Zed                    +
+-----------------------------+

¿Cómo podría seleccionar la Betafila obteniendo la posición actual de esa fila? El conjunto de resultados que estoy buscando sería algo como esto:

+-----------------------------+
+ id | position | name        +
+-----------------------------+
+  7 |        2 | Beta        +
+-----------------------------+

Puedo hacer un simple y SELECT * FROM tbl ORDER BY name ASCluego enumerar las filas en PHP, pero parece un desperdicio cargar un conjunto de resultados potencialmente grande solo para una sola fila.

leepowers
fuente
stackoverflow.com/questions/2520357/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:

120

Utilizar esta:

SELECT x.id, 
       x.position,
       x.name
  FROM (SELECT t.id,
               t.name,
               @rownum := @rownum + 1 AS position
          FROM TABLE t
          JOIN (SELECT @rownum := 0) r
      ORDER BY t.name) x
 WHERE x.name = 'Beta'

... para obtener un valor de posición único. Esta:

SELECT t.id,
       (SELECT COUNT(*)
          FROM TABLE x
         WHERE x.name <= t.name) AS position,
       t.name    
  FROM TABLE t      
 WHERE t.name = 'Beta'

... le dará a las corbatas el mismo valor. IE: Si hay dos valores en el segundo lugar, ambos tendrán una posición de 2 cuando la primera consulta dará una posición de 2 a uno de ellos y 3 a la otra ...

Ponis dios mio
fuente
@actual: No hay nada que decir - no hay alternativa, aparte de moverse a un competidor que soporta funciones analíticas (PostgreSQL, Oracle, SQL Server, DB2 ...)
OMG potros
2
@OMGPonies Olvídate de una coma después position, pero es perfecto.
pierallard
Sé que es una publicación bastante antigua, pero necesito una solución similar. Sin embargo, cuando se utilizan combinaciones internas, agrupar y ordenar por, el campo "posición" los ignora y el valor se confunde. ¿Alguna solución?
Daniel
@Daniel, deberías hacer una nueva pregunta y quizás referirte a esta.
PeerBr
@actual, dado que se trata de una consulta para una sola fila, no debería haber un problema de rendimiento significativo aquí. Es diferente si está tratando de obtener los rangos de una lista completa, pero podría simplemente "hacer trampa" y usar un rango implícito ordenando por puntos.
AgmLauncher
20

Esta es la única forma en la que puedo pensar:

SELECT `id`,
       (SELECT COUNT(*) FROM `table` WHERE `name` <= 'Beta') AS `position`,
       `name`
FROM `table`
WHERE `name` = 'Beta'
zerkms
fuente
2
+1 Buen truco ... Sin embargo, probablemente quieras usarlo name <= 'Beta'en su lugar
Daniel Vassallo
Este enfoque dará los mismos positionvalores para los empates.
OMG Ponies
(Eliminé mi comentario anterior, estaba equivocado) ... ¿Qué pasa si agregas un LIMIT 1allí? En caso de empate, obtendría solo una fila con la última posición del empate.
Daniel Vassallo
Si OP puede garantizar que el namecampo sea único, entonces no hay razón para hacer la consulta más compleja. Si no puede, esperemos sus expectativas de resultados para nombres empatados.
zerkms
8

Si la consulta es simple y el tamaño del conjunto de resultados devuelto es potencialmente grande, entonces puede intentar dividirlo en dos consultas.

La primera consulta con un criterio de filtrado restringido solo para recuperar datos de esa fila, y la segunda consulta usa la cláusula COUNTwith WHEREpara calcular la posición.

Por ejemplo en tu caso

Consulta 1:

SELECT * FROM tbl WHERE name = 'Beta'

Consulta 2:

SELECT COUNT(1) FROM tbl WHERE name >= 'Beta'

Usamos este enfoque en una tabla con un registro de 2M y esto es mucho más escalable que el enfoque de OMG Ponies.

Max
fuente
4

Las otras respuestas me parecen demasiado complicadas.

Aquí viene un ejemplo sencillo , digamos que tiene una tabla con columnas:

userid | points

y desea ordenar los ID de usuario por puntos y obtener la posición de la fila (la "clasificación" del usuario), luego usa:

SET @row_number = 0;

SELECT 
    (@row_number:=@row_number + 1) AS num, userid, points
FROM
    ourtable
ORDER BY points DESC

num le da la posición de la fila (clasificación).

Si tiene MySQL 8.0+, es posible que desee usar ROW_NUMBER ()

Kai Noack
fuente
2

La posición de una fila en la tabla representa cuántas filas son "mejores" que la fila de destino.

Entonces, debes contar esas filas.

SELECCIONAR CONTADOR (*) + 1 DESDE tableDONDE name<'Beta'

En caso de empate, se devuelve la posición más alta.

Si agrega otra fila con el mismo nombre de "Beta" después de la fila "Beta" existente, la posición devuelta seguirá siendo 2, ya que compartirían el mismo lugar en la clasificación.

Espero que esto ayude a las personas que buscarán algo similar en el futuro, ya que creo que el propietario de la pregunta ya resolvió su problema.

NVG
fuente
2

Tengo un problema muy, muy similar, por eso no haré la misma pregunta, pero compartiré aquí qué hice, tuve que usar también un grupo por y un pedido por AVG. Hay estudiantes, con firmas y socore, y tuve que clasificarlos (en otras palabras, primero calculé el AVG, luego los ordené en DESC, y finalmente necesitaba agregar la posición (rango para mí), así que lo hice algo muy similar a la mejor respuesta aquí, con pequeños cambios que se ajustan a mi problema):

Puse finalmente la positioncolumna (rango para mí) en el SELECT externo

SET @rank=0;
SELECT @rank := @rank + 1 AS ranking, t.avg, t.name
  FROM(SELECT avg(students_signatures.score) as avg, students.name as name
FROM alumnos_materia
JOIN (SELECT @rownum := 0) r
left JOIN students ON students.id=students_signatures.id_student
GROUP BY students.name order by avg DESC) t 
Damián Rafael Lattenero
fuente
Esta respuesta fue más fácil de entender que la aceptada. +1
Eric Seastrand
1

Estaba revisando la respuesta aceptada y parecía un poco complicado, así que aquí está la versión simplificada.

SELECT t,COUNT(*) AS position FROM t      
 WHERE name <= 'search string' ORDER BY name
Davinder Singh
fuente
0

Tengo problemas similares en los que necesito el rango ( índice ) de la tabla order by votes desc. Lo siguiente funciona bien para mí.

Select *, ROW_NUMBER() OVER(ORDER BY votes DESC) as "rank"
From "category_model"
where ("model_type" = ? and "category_id" = ?)
Bedram Tamang
fuente
-9

puede ser lo que necesita es agregar sintaxis

LIMIT

así que usa

SELECT * FROM tbl ORDER BY name ASC LIMIT 1

si solo necesitas una fila ...

Eka Rudito
fuente
esta respuesta no resuelve el problema aquí. Podría considerar eliminarlo
Damián Rafael Lattenero