MySQL ottiene la posizione della riga in ORDER BY


88

Con la seguente tabella MySQL:

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

Come posso selezionare una singola riga E la sua posizione tra le altre righe nella tabella, se ordinata per name ASC. Quindi, se i dati della tabella hanno questo aspetto, se ordinati per nome:

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

Come posso selezionare la Betariga ottenendo la posizione corrente di quella riga? Il set di risultati che sto cercando sarebbe qualcosa del genere:

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

Posso fare un semplice SELECT * FROM tbl ORDER BY name ASCquindi enumerare le righe in PHP, ma sembra uno spreco caricare un gruppo di risultati potenzialmente grande solo per una singola riga.



Risposte:


122

Usa questo:

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'

... per ottenere un valore di posizione univoco. Questo:

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

... darà alle cravatte lo stesso valore. IE: Se ci sono due valori al secondo posto, avranno entrambi una posizione di 2 quando la prima query darà una posizione di 2 a uno di loro e 3 all'altra ...


@actual: non c'è niente da dire - non c'è alternativa, se non quella di passare a un concorrente che supporta funzioni analitiche (PostgreSQL, Oracle, SQL Server, DB2 ...)
OMG Ponies

2
@OMGPonies Dimentica solo una virgola dopo position, ma è perfetto.
pierallard

So che è un post piuttosto vecchio, ma ho bisogno di una soluzione simile. Tuttavia, quando si utilizzano join interni, raggruppa per e ordina per, il campo "posizione" li ignora e il valore viene confuso. Eventuali soluzioni?
Daniel

@Daniel dovresti fare una nuova domanda e forse fare riferimento a questa.
PeerBr

@actual, poiché si tratta di una query per una singola riga, non dovrebbe esserci un problema di prestazioni significativo qui. È diverso se stai cercando di ottenere i ranghi di un elenco completo, ma potresti semplicemente "barare" e utilizzare un rango implicito ordinando per punti.
AgmLauncher

20

Questo è l'unico modo in cui riesco a pensare:

SELECT `id`,
       (SELECT COUNT(*) FROM `table` WHERE `name` <= 'Beta') AS `position`,
       `name`
FROM `table`
WHERE `name` = 'Beta'

2
+1 Bel trucco ... Tuttavia probabilmente vorresti usare name <= 'Beta'invece
Daniel Vassallo

Questo approccio darà gli stessi positionvalori per i legami.
OMG Ponies

(Eliminato il mio commento precedente - mi sbagliavo) ... E se aggiungessi un LIMIT 1lì? In caso di pareggio, otterrai solo una riga con l'ultima posizione del pareggio.
Daniel Vassallo

Se OP può garantire che il namecampo sia univoco, non c'è motivo di rendere la query più complessa. Se non può, allora aspettiamo le sue aspettative sui risultati per i nomi in parità.
zerkms

8

Se la query è semplice e la dimensione del set di risultati restituito è potenzialmente grande, puoi provare a suddividerla in due query.

La prima query con criteri di filtraggio ristretti solo per recuperare i dati di quella riga e la seconda query utilizza la clausola COUNTwith WHEREper calcolare la posizione.

Ad esempio nel tuo caso

Domanda 1:

SELECT * FROM tbl WHERE name = 'Beta'

Domanda 2:

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

Usiamo questo approccio in una tabella con 2 milioni di record e questo è molto più scalabile dell'approccio di OMG Ponies.


5

Le altre risposte mi sembrano troppo complicate.

Ecco un semplice esempio , supponiamo di avere una tabella con colonne:

userid | points

e vuoi ordinare gli userid per punti e ottenere la posizione della riga (la "classifica" dell'utente), quindi usi:

SET @row_number = 0;

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

num ti dà la posizione di riga (classifica).

Se hai MySQL 8.0+, potresti voler usare ROW_NUMBER ()


2

La posizione di una riga nella tabella rappresenta il numero di righe "migliori" rispetto alla riga di destinazione.

Quindi, devi contare quelle righe.

SELEZIONA COUNT (*) + 1 DA tableDOVE name<"Beta"

In caso di parità, viene restituita la posizione più alta.

Se aggiungi un'altra riga con lo stesso nome di "Beta" dopo la riga "Beta" esistente, la posizione restituita sarebbe ancora 2, poiché condividerebbe lo stesso posto nella classificazione.

Spero che questo aiuti le persone che cercheranno qualcosa di simile in futuro, poiché credo che il proprietario della domanda abbia già risolto il suo problema.


2

Ho un problema molto molto simile, ecco perché non farò la stessa domanda, ma condividerò qui cosa ho fatto, ho dovuto usare anche un gruppo di e ordinare da AVG. Ci sono studenti, con firme e socore, e ho dovuto classificarli (in altre parole, ho prima calcolato l'AVG, poi li ho ordinati in DESC e infine ho dovuto aggiungere la posizione (classifica per me), quindi l'ho fatto qualcosa di molto simile come la migliore risposta qui, con piccole modifiche che si adattano al mio problema):

Infine metto la positioncolonna (rank for me) nella SELECT esterna

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 

Questa risposta era più facile da capire di quella accettata. +1
Eric Seastrand

1

Stavo esaminando la risposta accettata e sembrava un po 'complicata, quindi ecco la versione semplificata.

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

0

Ho tipi di problemi simili in cui richiedo il rango ( Indice ) della tabella order by votes desc. Quanto segue funziona bene con me.

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

-9

potrebbe essere ciò di cui hai bisogno con aggiungi sintassi

LIMIT

quindi usa

SELECT * FROM tbl ORDER BY name ASC LIMIT 1

se ti serve solo una riga ..


questa risposta non risolve il problema qui. Potresti considerare di cancellarlo
Damián Rafael Lattenero
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.