MySQL obtient la position de la ligne dans ORDER BY

86

Avec la table MySQL suivante:

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

Comment puis-je sélectionner une seule ligne ET sa position parmi les autres lignes du tableau, une fois triées par name ASC. Donc, si les données de la table ressemblent à ceci, lorsqu'elles sont triées par nom:

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

Comment pourrais-je sélectionner la Betaligne pour obtenir la position actuelle de cette ligne? L'ensemble de résultats que je recherche serait quelque chose comme ceci:

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

Je peux faire un simple SELECT * FROM tbl ORDER BY name ASCpuis énumérer les lignes en PHP, mais il semble inutile de charger un ensemble de résultats potentiellement volumineux juste pour une seule ligne.

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

Réponses:

120

Utilisez ceci:

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'

... pour obtenir une valeur de position unique. Ce:

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

... donnera aux cravates la même valeur. IE: S'il y a deux valeurs à la deuxième place, elles auront toutes les deux une position de 2 lorsque la première requête donnera une position de 2 à l'une d'elles et de 3 à l'autre ...

Poneys OMG
la source
@actual: Il n'y a rien à dire - il n'y a pas d'autre alternative que de passer à un concurrent prenant en charge les fonctions analytiques (PostgreSQL, Oracle, SQL Server, DB2 ...)
OMG Ponies
2
@OMGPonies Oubliez juste une virgule après position, mais c'est parfait.
pierallard
Je sais que c'est un article assez ancien, mais j'ai besoin d'une solution similaire. Cependant, lorsque vous utilisez des jointures internes, regrouper et classer par, le champ "position" les ignore et la valeur est confondue. Des solutions?
Daniel
@Daniel, vous devriez poser une nouvelle question et peut-être vous référer à celle-ci.
PeerBr
@actual, puisqu'il s'agit d'une requête pour une seule ligne, il ne devrait pas y avoir de problème de performances significatif ici. C'est différent si vous essayez d'obtenir les rangs d'une liste complète, mais vous pouvez simplement «tricher» et utiliser un rang implicite en triant par points.
AgmLauncher
20

C'est la seule façon dont je peux penser:

SELECT `id`,
       (SELECT COUNT(*) FROM `table` WHERE `name` <= 'Beta') AS `position`,
       `name`
FROM `table`
WHERE `name` = 'Beta'
zerkms
la source
2
+1 Belle astuce ... Cependant, vous voudrez probablement l'utiliser à la name <= 'Beta'place
Daniel Vassallo
Cette approche donnera les mêmes positionvaleurs pour les cravates.
OMG Ponies
(Supprimé mon commentaire précédent - je me suis trompé) ... Et si vous ajoutez un LIMIT 1là-dedans? En cas d'égalité, vous n'obtiendrez qu'une seule ligne avec la dernière position de l'égalité.
Daniel Vassallo
Si OP peut garantir que ce namechamp est unique, il n'y a aucune raison de rendre la requête plus complexe. S'il ne peut pas - attendons ses attentes de résultat pour les noms à égalité.
zerkms
8

Si la requête est simple et que la taille du jeu de résultats renvoyé est potentiellement importante, vous pouvez essayer de la diviser en deux requêtes.

La première requête avec un critère de filtrage restreint juste pour récupérer les données de cette ligne, et la deuxième requête utilise la clause COUNTwith WHEREpour calculer la position.

Par exemple dans votre cas

Requête 1:

SELECT * FROM tbl WHERE name = 'Beta'

Requête 2:

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

Nous utilisons cette approche dans une table avec 2M d'enregistrement et c'est beaucoup plus évolutif que l'approche d'OMG Ponies.

Max
la source
4

Les autres réponses me semblent trop compliquées.

Voici un exemple simple , disons que vous avez une table avec des colonnes:

userid | points

et vous voulez trier les userids par points et obtenir la position de la ligne (le "classement" de l'utilisateur), alors vous utilisez:

SET @row_number = 0;

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

num vous donne la position de ligne (classement).

Si vous avez MySQL 8.0+, vous pouvez utiliser ROW_NUMBER ()

Kai Noack
la source
2

La position d'une ligne dans le tableau représente le nombre de lignes "meilleures" que la ligne ciblée.

Donc, vous devez compter ces lignes.

SELECT COUNT (*) + 1 FROM tableWHERE name<'Beta'

En cas d'égalité, la position la plus élevée est renvoyée.

Si vous ajoutez une autre ligne avec le même nom "Beta" après la ligne "Beta" existante, la position renvoyée sera toujours 2, car ils partageraient la même place dans la classification.

J'espère que cela aidera les gens qui chercheront quelque chose de similaire à l'avenir, car je pense que le propriétaire de la question a déjà résolu son problème.

NVG
la source
2

J'ai un problème très similaire, c'est pourquoi je ne poserai pas la même question, mais je vais partager ici ce que j'ai fait, j'ai dû utiliser aussi un groupe par, et commander par AVG. Il y a des étudiants, avec des signatures et socore, et j'ai dû les classer (en d'autres termes, je calcule d'abord l'AVG, puis les ordonne en DESC, et enfin j'ai besoin d'ajouter la position (rang pour moi), donc j'ai quelque chose de Très similaire comme la meilleure réponse ici, avec quelques changements qui s'ajustent à mon problème):

Je mets enfin la positioncolonne (rank for me) dans le SELECT externe

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
la source
Cette réponse était plus facile à comprendre que celle acceptée. +1
Eric Seastrand
1

J'étais en train de parcourir la réponse acceptée et cela me semblait un peu compliqué, alors en voici la version simplifiée.

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

J'ai des types de problèmes similaires où j'ai besoin du rang ( index ) de la table order by votes desc. Ce qui suit fonctionne très bien pour moi.

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

peut être ce dont vous avez besoin avec une syntaxe d'ajout

LIMIT

alors utilise

SELECT * FROM tbl ORDER BY name ASC LIMIT 1

si vous n'avez besoin que d'une ligne ..

Eka Rudito
la source
cette réponse ne résout pas le problème ici. Vous pourriez envisager de le supprimer
Damián Rafael Lattenero