ROW_NUMBER () dans MySQL


281

Existe-t-il un bon moyen dans MySQL pour répliquer la fonction SQL Server ROW_NUMBER()?

Par exemple:

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

Ensuite, je pourrais, par exemple, ajouter une condition à limiter intRowà 1 pour obtenir une seule ligne avec le plus haut col3pour chaque (col1, col2)paire.


9
Taggé avec greatest-n-per-grouppour vous guider vers des questions similaires.
Bill Karwin,

1
Suppression de la Sql-Serverbalise car il s'agissait de l'élément le plus voté dans la recherche de balises combinées, mais pas vraiment pertinent pour SQL Server.
Martin Smith

pour une simple fonction de numéro de ligne mysql, consultez datamakessense.com/mysql-rownum-row-number-function
AdrianBR

Pour MySQL, les seules solutions efficaces sont discutées dans mysql.rjweb.org/doc.php/groupwise_max . De nombreuses solutions publiées nécessitent une analyse complète de la table (ou pire).
Rick James

2
Solution auto-promouvante sans vergogne pour une véritable fonction analytique ROW_NUMBER, RANK, DESNSE_RANK dans MySQL
Kenneth Xu

Réponses:


102

Je veux la ligne avec le col3 le plus élevé pour chaque paire (col1, col2).

C'est un maximum par groupe , l'une des questions SQL les plus fréquemment posées (car il semble que cela devrait être facile, mais en fait ce n'est pas le cas).

Je plump souvent pour une auto-jointure nulle:

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;

"Obtenez les lignes du tableau pour lesquelles aucune autre ligne avec col1, col2 correspondant n'a un col3 supérieur." (Vous remarquerez cela et la plupart des autres solutions maximales par groupe renverront plusieurs lignes si plusieurs lignes ont le même col1, col2, col3. Si c'est un problème, vous devrez peut-être un post-traitement.)


2
Mais que faire s'il y a deux valeurs maximales de col3 pour une paire (col1, col2)? Vous vous retrouveriez avec deux rangées.
Paul

@Paul: oui! Je viens d'ajouter une note à ce sujet dans la réponse il y a un tic. Vous pouvez généralement facilement supprimer des lignes supplémentaires indésirables dans la couche d'application par la suite de manière aléatoire, mais si vous avez beaucoup de lignes avec le même col3, cela peut être problématique.
bobince

1
bobince, la solution est devenue plutôt populaire ici sur SO, mais j'ai une question. La solution est fondamentalement la même que si quelqu'un essayait de trouver le plus grand identifiant avec la requête suivante: SELECT t1.id FROM test t1 LEFT JOIN test t2 ON t1.id>t2.id WHERE t2.id IS NULL;Ne nécessite-t-il pas des n*n/2 + n/2comparaisons IS NULL pour trouver la seule ligne? Y a-t-il des optimisations que je ne vois pas? J'ai essayé de poser la question similaire à Bill dans un autre fil, mais il semble l'avoir ignoré.
newtover

2
@Paul - Pour résoudre le cas où plusieurs lignes correspondent au maximum par groupe et que vous souhaitez en saisir une seule, vous pouvez toujours ajouter la clé primaire dans la logique de la clause ON pour rompre le lien ... SELECT table t0.col3 FROM AS t0 LEFT JOIN table AS t1 ON t0.col1 = t1.col1 AND t0.col2 = t1.col2 AND (t1.col3, t1.pk)> (t0.col3, t0.pk) O t t1.col1 EST NUL;
Jon Armstrong - Xgc

2
Ce serait plus lisible commeSELECT 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

Il n'y a pas de fonctionnalité de classement dans MySQL. Le plus proche que vous pouvez obtenir est d'utiliser une variable:

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

alors comment cela fonctionnerait-il dans mon cas? J'aurais besoin de deux variables, une pour chacun de col1 et col2? Col2 devrait être réinitialisé d'une manière ou d'une autre lorsque col1 a changé ..?

Oui. S'il s'agissait d'Oracle, vous pourriez utiliser la fonction LEAD pour atteindre un pic à la valeur suivante. Heureusement, Quassnoi couvre la logique de ce que vous devez implémenter dans MySQL .


1
Hmm .... alors comment cela fonctionnerait-il dans mon cas? J'aurais besoin de deux variables, une pour chacun de col1 et col2? Col2 devrait être réinitialisé d'une manière ou d'une autre lorsque col1 a changé ..?
Paul

Merci ... comme je l'ai dit plus haut, cette réponse est également acceptée par Bobince, mais je ne peux en cocher qu'une seule :-)
Paul

9
L'affectation et la lecture de variables définies par l'utilisateur dans la même instruction ne sont pas fiables. ceci est documenté ici: dev.mysql.com/doc/refman/5.0/en/user-variables.html : "En règle générale, vous ne devez jamais affecter une valeur à une variable utilisateur et lire la valeur dans la même instruction. Vous pouvez obtenir les résultats escomptés, mais cela n'est pas garanti. L'ordre d'évaluation des expressions impliquant des variables utilisateur n'est pas défini et peut changer en fonction des éléments contenus dans une instruction donnée. "
Roland Bouman

1
@Roland: Je n'ai testé que sur de petits ensembles de données, je n'ai eu aucun problème. Dommage que MySQL n'ait pas encore abordé la fonctionnalité - la demande est en cours depuis 2008
OMG Ponies

2
Cela semble être un comportement indéfini, comme le note Roland. par exemple, cela donne des résultats totalement incorrects pour une table que j'ai essayée: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

Je finis toujours par suivre ce schéma. Compte tenu de ce tableau:

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

Vous pouvez obtenir ce résultat:

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

En exécutant cette requête, qui n'a besoin d'aucune variable définie:

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

J'espère que cela pourra aider!


1
si les colonnes sont VARCHAR ou CHAR, comment pouvez-vous gérer cela avec cette structure?
Tushar

3
Tu es génial Mosty, je cherche exactement ça
luckykrrish

Je viens de donner cette réponse en utilisant votre logique pour row_number. Merci.
Utsav

@Tushar les opérateurs <, >, <=, >=poignée CHAR et types de données VARCHAR sur l' ordre alphabétique; Je pense que c'est exactement ce que vous recherchez.
alex

1
@AlmazVildanov, vous devriez pouvoir utiliser cette requête simplement comme sous-requête pour filtrer. row_numbers <= 2 Et merci énormément pour cette réponse Mosty, c'est parfait!
Zax

61
SELECT 
    @i:=@i+1 AS iterator, 
    t.*
FROM 
    tablename AS t,
    (SELECT @i:=0) AS foo

1
Le premier: = semble manquer dans la réponse @OMG Ponies. Merci d'avoir publié ce Peter Johnson.
sholsinger

Je suppose (SELECT @i: = 0) AS foo devrait être la première table de l'instruction FROM, surtout si d'autres tables utilisent des sous-sélections
andig

Pourquoi avez-vous même besoin du '.. as foo'?
Tom Chiverton

@TomChiverton S'il est manquant, vous obtenez: "Code d'erreur: 1248. Chaque table dérivée doit avoir son propre alias"
ExStackChanger

1
L'affectation des grades ici est complètement indéfinie et cela ne répond même pas à la question
jberryman

27

Consultez cet article, il montre comment imiter SQL ROW_NUMBER () avec une partition par dans MySQL. J'ai rencontré ce même scénario dans une mise en œuvre de WordPress. J'avais besoin de ROW_NUMBER () et ce n'était pas là.

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

L'exemple de l'article utilise une seule partition par champ. Pour partitionner par champs supplémentaires, vous pouvez faire quelque chose comme ceci:

  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 

L'utilisation de concat_ws gère les valeurs nulles. J'ai testé cela sur 3 champs en utilisant un int, une date et un varchar. J'espère que cela t'aides. Consultez l'article car il décompose cette requête et l'explique.


1
Impressionnant. Cela fait en fait le partitionnement. Très pratique
Stuart Watt

1
Comparé à l'auto-jointure, c'est beaucoup plus efficace, mais il y a un problème avec la logique, l'ordre doit se produire avant de calculer row_num, la concaténation n'est pas non plus nécessaire. `` `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 vous avez besoin de mettre ceci dans une sous-requête, ajoutez limit 18446744073709551615à la order byclause force .
xmedeko

concat_wsavec une chaîne vide ''est dangereuse: concat_ws('',12,3) = concat_ws('',1,23). Mieux vaut utiliser un séparateur '_'ou utiliser la solution @Kenneth Xu.
xmedeko

le lien de l'op est mort; archive du lien ici
user2426679

25

Depuis MySQL 8.0.0et au-dessus, vous pouvez utiliser nativement des fonctions fenêtrées.

1.4 Quoi de neuf dans MySQL 8.0 :

Fonctions de fenêtre.

MySQL prend désormais en charge les fonctions de fenêtre qui, pour chaque ligne d'une requête, effectuent un calcul en utilisant les lignes liées à cette ligne. Il s'agit notamment de fonctions telles que RANK (), LAG () et NTILE (). De plus, plusieurs fonctions d'agrégation existantes peuvent désormais être utilisées comme fonctions de fenêtre; par exemple, SUM () et AVG ().

ROW_NUMBER () over_clause :

Renvoie le numéro de la ligne actuelle dans sa partition. Les nombres de lignes vont de 1 au nombre de lignes de partition.

ORDER BY affecte l'ordre dans lequel les lignes sont numérotées. Sans ORDER BY, la numérotation des lignes est indéterminée.

Démo:

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


1
soupir ... enfin!
Used_By_Already

15

Je voterais également pour la solution de Mosty Mostacho avec une modification mineure de son code de requête:

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

Ce qui donnera le même résultat:

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

pour la table:

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

À la seule différence que la requête n'utilise pas JOIN et GROUP BY, en s'appuyant à la place sur select imbriqué.


Est-ce censé être mieux? Ils semblent tous deux quadratiques, mais je ne sais pas comment interpréter la sortie EXPLAIN
jberryman

En fait, les sélections imbriquées ne sont pas très bien optimisées dans MySQL, donc cette réponse est juste pour la démonstration d'une technique de requête. Les exemples à base de variables ci-dessus fonctionnent mieux pour la plupart des cas pratiques, je suppose.
abcdn

1
Je ne suis pas convaincu qu'aucune des réponses basées sur des variables n'utilise réellement un comportement défini ...
jberryman

Je suis désolé, je ne suis pas sûr d'avoir compris ce que vous entendiez par "comportement défini". Voulez-vous dire que cela ne fonctionne pas pour vous, ou êtes-vous simplement préoccupé par le fait qu'il n'est pas documenté?
abcdn

1
"Comportement indéfini" signifie qu'il n'est pas documenté pour fonctionner et / ou documenté pour ne pas être garanti de fonctionner. Voir les citations de documentation et les liens dans les commentaires sur cette page. Il pourrait retourner ce que l'on (maladroitement) veut / devine / émet des hypothèses / fantasme. Pour certaines versions de l'implémentation, certaines expressions de requête utilisant l'incrémentation CASE et l'utilisation de variables ont été montrées comme fonctionnant par les programmeurs de Percona en regardant le code. Cela pourrait changer avec n'importe quelle version.
philipxy

12

Je définirais une fonction:

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

alors je pourrais faire:

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

Maintenant, vous n'avez pas de sous-requête, que vous ne pouvez pas avoir dans les vues.


Fonctionne avec une limitation: si vous exécutez la requête plusieurs fois, vous obtiendrez des fakeIds toujours croissants pour le même jeu de résultats
Stephan Richter

vous pouvez envoyer set @fakeId = 0; chaque fois que vous voulez exécuter la requête, pas optimale mais fonctionne
jmpeace

Un problème vraiment étrange se produit si vous supprimez DETERMINISTIC. Ensuite, le fakeId est incorrect lors de l'utilisation de order by. Pourquoi est-ce?
Chris Muench

8

requête pour row_number dans mysql

set @row_number=0;
select (@row_number := @row_number +1) as num,id,name from sbs

Cela peut être utilisé sur les requêtes UPDATE? J'essaye mais j'obtiens une erreur "données tronquées pour la colonne ...".
Diego

1
Si quelqu'un est intéressé à l'utiliser sur UPDATE, il doit être utilisé comme sous-requête pour fonctionner. UPDATE <table> SET <field> = (SELECT \ @row_number: = \ @row_number +1) COMMANDEZ PAR <votre colonne de commande>; La colonne de commande détermine l'ordre des valeurs des lignes.
Diego

8

Il n'y a pas de fonction comme rownum, row_num()dans MySQL mais le chemin est comme ci-dessous:

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

4

La solution que j'ai trouvée la plus efficace consiste à utiliser une sous-requête comme celle-ci:

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

Les colonnes PARTITION BY sont simplement comparées à '=' et séparées par AND. Les colonnes ORDER BY seraient comparées à «<» ou «>» et séparées par OR.

J'ai trouvé que c'était très flexible, même si c'était un peu cher.


4

La fonctionnalité de numéro de version ne peut pas être imitée. Vous obtiendrez peut-être les résultats escomptés, mais vous serez très probablement déçu à un moment donné. Voici ce que dit la documentation mysql:

Pour d'autres instructions, telles que SELECT, vous pouvez obtenir les résultats escomptés, mais cela n'est pas garanti. Dans la déclaration suivante, vous pourriez penser que MySQL évaluera d'abord @a puis effectuera une affectation ensuite: SELECT @a, @a: = @ a + 1, ...; Cependant, l'ordre d'évaluation des expressions impliquant des variables utilisateur n'est pas défini.

Cordialement, Georgi.


Je ne suis pas. Comment "@i: = @i + 1 comme position" n'est-il pas un remplacement direct de "ROW_NUMBER () sur (ordre par somme (score) desc) comme position"?
Tom Chiverton

1
@TomChiverton Parce que son comportement n'est pas défini, comme le dit le manuel ici.
philipxy


2

Je ne vois pas de réponse simple couvrant la partie "PARTITION PAR" alors voici la mienne:

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 clause ORDER BY doit refléter votre besoin de ROW_NUMBER. Il y a donc déjà une limitation claire: vous ne pouvez pas avoir plusieurs "émulations" ROW_NUMBER de ce formulaire en même temps.
  • L'ordre de la "colonne calculée" est important . Si mysql calcule ces colonnes dans un autre ordre, cela peut ne pas fonctionner.
  • Dans cet exemple simple, je n'en mets qu'une mais vous pouvez avoir plusieurs parties "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

1

Un peu tard mais peut aussi aider quelqu'un qui cherche des réponses ...

Exemple entre les lignes / numéro de ligne - requête récursive qui peut être utilisée dans n'importe quel 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

2
Désolé mais pour autant que je sache, MySQL ne prend pas en charge les expressions de table courantes .
Álvaro González

il le fait maintenant ... @ ÁlvaroGonzález MySQL 8 ne prend en charge que les fonctions CTE et fenêtre, donc cette réponse n'a pas vraiment de sens à utiliser dans les anciennes versions de MySQL ..
Raymond Nijland

1

Cela permet d'obtenir les mêmes fonctionnalités que ROW_NUMBER () ET PARTITION BY dans 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

1

Aussi un peu tard mais aujourd'hui j'avais le même besoin donc j'ai fait une recherche sur Google et enfin une approche générale simple trouvée ici dans l'article de Pinal Dave http://blog.sqlauthority.com/2014/03/09/mysql-reset-row -number-for-each-group-partition-by-row-number /

Je voulais me concentrer sur la question initiale de Paul (c'était aussi mon problème), donc je résume ma solution comme un exemple de travail.

Beacuse nous voulons partitionner sur deux colonnes Je créerais une variable SET pendant l'itération pour identifier si un nouveau groupe a été démarré.

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

Le 3 signifie au premier paramètre de MAKE_SET que je veux les deux valeurs dans le SET (3 = 1 | 2). Bien sûr, si nous n'avons pas deux colonnes ou plus construisant les groupes, nous pouvons éliminer l'opération MAKE_SET. La construction est exactement la même. Cela fonctionne pour moi au besoin. Un grand merci à Pinal Dave pour sa démonstration claire.


1
Notez que ORDER BYdans une sous-requête, elle peut être ignorée (voir mariadb.com/kb/en/mariadb/… ). La solution suggérée est d'ajouter LIMIT 18446744073709551615à la sous-requête, ce qui force un tri. Cependant, cela pourrait entraîner des problèmes de performances et n'est pas valable pour les énormes tables vraiment
flippantes

1

Cela pourrait également être une solution:

SET @row_number = 0;

SELECT 
    (@row_number:=@row_number + 1) AS num, firstName, lastName
FROM
    employees

Cependant, il ne fait aucun partitionnement et n'est pas très différent d'une réponse citée plus haut
Caius Jard

1

MySQL prend en charge ROW_NUMBER () depuis la version 8.0+ .

Si vous utilisez MySQL 8.0 ou version ultérieure, vérifiez-la fonction ROW_NUMBER (). Sinon, vous avez émulé la fonction ROW_NUMBER ().

Row_number () est une fonction de classement qui renvoie un numéro séquentiel d'une ligne, à partir de 1 pour la première ligne.

pour une version plus ancienne,

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

1

Important: veuillez envisager la mise à niveau vers MySQL 8+ et utiliser la fonction ROW_NUMBER () définie et documentée, et abandonner les anciens hacks liés à une ancienne version limitée de MySQL.

Maintenant, voici un de ces hacks:

Les réponses ici qui utilisent la plupart du temps des variables dans la requête semblent ignorer le fait que la documentation dit (paraphrase):

Ne vous fiez pas aux éléments de la liste SELECT en cours d'évaluation de haut en bas. Ne pas affecter de variables dans un élément SELECT et les utiliser dans un autre

En tant que tel, il y a un risque qu'ils produisent la mauvaise réponse, car ils font généralement un

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

Si celles-ci sont évaluées de bas en haut, le numéro de ligne cessera de fonctionner (pas de partitions)

Nous devons donc utiliser quelque chose avec un ordre d'exécution garanti. Entrez CASE WHEN:

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

Comme le contour ld, l'ordre d'affectation de prevcol est important - prevcol doit être comparé à la valeur de la ligne actuelle avant de lui affecter une valeur à partir de la ligne actuelle (sinon ce serait la valeur col des lignes actuelles, pas la valeur col de la ligne précédente) .

Voici comment cela s'intègre:

  • Le premier QUAND est évalué. Si le col de cette ligne est le même que le col de la ligne précédente, @r est incrémenté et renvoyé par CASE. Ces valeurs de led de retour sont stockées dans @r. C'est une caractéristique de MySQL que l'affectation renvoie la nouvelle valeur de ce qui est attribué à @r dans les lignes de résultat.

  • Pour la première ligne du jeu de résultats, @prevcol est null (il est initialisé à null dans la sous-requête), donc ce prédicat est faux. Ce premier prédicat renvoie également false chaque fois que col change (la ligne actuelle est différente de la ligne précédente). Cela entraîne l'évaluation du deuxième QUAND.

  • Le deuxième prédicat WHEN est toujours faux, et il existe uniquement pour attribuer une nouvelle valeur à @prevcol. Parce que le col de cette ligne est différent du col de la ligne précédente (nous le savons parce que s'il était le même, le premier QUAND aurait été utilisé), nous devons attribuer la nouvelle valeur pour la conserver pour les tests la prochaine fois. Étant donné que l'affectation est effectuée et que le résultat de l'affectation est comparé à null et que tout ce qui est égal à null est faux, ce prédicat est toujours faux. Mais au moins l'évaluer a fait son travail en gardant la valeur de col de cette ligne, donc il peut être évalué par rapport à la valeur de col de la ligne suivante

  • Parce que le deuxième WHEN est faux, cela signifie que dans les situations où la colonne que nous partitionnons par (col) a changé, c'est ELSE qui donne une nouvelle valeur pour @r, en redémarrant la numérotation à partir de 1

Nous ceci arrivons à une situation où ceci:

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

A la forme générale:

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

Notes de bas de page:

  • Le p dans pcol signifie "partition", le o dans ocol signifie "ordre" - dans la forme générale, j'ai supprimé le "prev" du nom de la variable pour réduire l'encombrement visuel

  • Les crochets autour (@pcolX := colX) = nullsont importants. Sans eux, vous affecterez null à @pcolX et les choses cesseront de fonctionner

  • C'est un compromis que l'ensemble de résultats doit également être ordonné par les colonnes de partition, pour que la comparaison de la colonne précédente fonctionne. Vous ne pouvez donc pas avoir votre numéro de commande ordonné selon une colonne, mais votre jeu de résultats ordonné à une autre. performance

  • Je n'y ai pas exploré au-delà du test du fonctionnement de la méthode, mais s'il y a un risque que les prédicats du deuxième WHEN soient optimisés (tout ce qui se compare à null est nul / faux alors pourquoi s'embêter à exécuter l'affectation) et non exécuté , il s'arrête également. Cela ne semble pas se produire dans mon expérience, mais je serai heureux d'accepter les commentaires et de proposer une solution si cela pouvait raisonnablement se produire

  • Il peut être judicieux de convertir les valeurs nulles qui créent @pcolX en types réels de vos colonnes, dans la sous-requête qui crée les variables @pcolX, à savoir: select @pcol1 := CAST(null as INT), @pcol2 := CAST(null as DATE)


Il n'y a aucune justification à cela. Tout comme les autres réponses qui affectent et lisent la même variable.
philipxy

Pouvez-vous fournir plus de détails phil?
Caius Jard du

Voir mes autres commentaires sur cette page. Googler le site: stackoverflow.com "philipxy" variable mysql (définir OU assigner OU assigner OU écrire) lire ': Une réponse par moi et un rapport de bogue lié dans un commentaire par moi à cette question où la réponse acceptée cite le manuel encore immédiatement dans les affirmations, il est OK de faire quelque chose en contradiction avec cela. Lisez le manuel concernant les variables et la réaffectation.
philipxy


Je comprends votre inquiétude
Caius Jard

0

Ce n'est pas la solution la plus robuste - mais si vous cherchez simplement à créer un classement partitionné sur un champ avec seulement quelques valeurs différentes, il peut ne pas être difficile d'utiliser un cas où la logique avec autant de variables que vous le souhaitez.

Quelque chose comme ça a fonctionné pour moi dans le passé:

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

J'espère que cela a du sens / aide!


-1

Cela fonctionne parfaitement pour moi de créer RowNumber lorsque nous avons plus d'une colonne. Dans ce cas, deux colonnes.

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

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

1
Veuillez essayer de formater toutes les réponses et donner un contexte supplémentaire quant à ce que vous essayez de faire. Pour le moment, ce n'est rien d'autre qu'un texte mal formaté.
Yannick Meeus

2
Cela ne semble pas avoir de relation avec la question d'origine. Si vous avez une question à vous poser, veuillez la poser séparément.
Jeroen Mostert, le

-5
SELECT 
    col1, col2, 
    count(*) as intRow
FROM Table1
GROUP BY col1,col2
ORDER BY col3 desc
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.