ROW_NUMBER () in MySQL


281

Gibt es in MySQL eine gute Möglichkeit, die SQL Server-Funktion zu replizieren ROW_NUMBER()?

Beispielsweise:

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

Dann könnte ich zum Beispiel eine Bedingung hinzufügen, die intRowauf 1 begrenzt ist, um eine einzelne Zeile mit der höchsten col3für jedes (col1, col2)Paar zu erhalten.


9
Markiert mit greatest-n-per-group, um Sie zu ähnlichen Fragen zu führen.
Bill Karwin

1
Das Sql-ServerTag wurde entfernt, da dies das Element mit der höchsten Bewertung in der kombinierten Tag-Suche war, aber für SQL Server nicht wirklich relevant ist.
Martin Smith

Eine einfache MySQL-Zeilennummernfunktion finden Sie unter datamakessense.com/mysql-rownum-row-number-function
AdrianBR

Für MySQL werden die einzigen effizienten Lösungen in mysql.rjweb.org/doc.php/groupwise_max erläutert . Viele veröffentlichte Lösungen erfordern einen vollständigen Tabellenscan (oder noch schlimmer).
Rick James

Antworten:


102

Ich möchte die Zeile mit der höchsten Spalte3 für jedes Paar (Spalte1, Spalte2).

Das ist ein gruppenweises Maximum , eine der am häufigsten gestellten SQL-Fragen (da es so scheint, als ob es einfach sein sollte, aber eigentlich nicht).

Ich plumpse oft für einen Null-Self-Join:

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;

"Holen Sie sich die Zeilen in der Tabelle, für die keine andere Zeile mit übereinstimmendem col1, col2 ein höheres col3 hat." (Sie werden feststellen, dass diese und die meisten anderen gruppenweise maximalen Lösungen mehrere Zeilen zurückgeben, wenn mehr als eine Zeile dieselbe Spalte1, Spalte2, Spalte3 hat. Wenn dies ein Problem ist, müssen Sie möglicherweise nachbearbeitet werden.)


2
Was aber, wenn es zwei Maximalwerte von col3 für ein (col1, col2) Paar gibt? Sie würden mit zwei Reihen enden.
Paul

@ Paul: ja! Habe gerade einen Hinweis dazu in der Antwort vor einem Tic hinzugefügt. Normalerweise können Sie unerwünschte zusätzliche Zeilen leicht nach dem Zufallsprinzip in der Anwendungsschicht ablegen. Wenn Sie jedoch viele Zeilen mit derselben Spalte3 haben, kann dies problematisch sein.
Bobince

1
Bobince, die Lösung wurde hier auf SO ziemlich populär, aber ich habe eine Frage. Die Lösung ist im Grunde die gleiche, als würde jemand versuchen, die größte ID mit der folgenden Abfrage zu finden: SELECT t1.id FROM test t1 LEFT JOIN test t2 ON t1.id>t2.id WHERE t2.id IS NULL;Erfordert es keine n*n/2 + n/2IS NULL-Vergleiche, um die einzelne Zeile zu finden? Gibt es Optimierungen, die ich nicht sehe? Ich habe versucht, Bill in einem anderen Thread eine ähnliche Frage zu stellen, aber er scheint sie ignoriert zu haben.
Newtover

2
@Paul - Um den Fall zu beheben, in dem mehrere Zeilen vorhanden sind, die dem Maximum pro Gruppe entsprechen und Sie nur eine abrufen möchten, können Sie jederzeit den Primärschlüssel in die ON-Klausel-Logik einfügen, um die Bindung zu lösen ... SELECT t0.col3 FROM-Tabelle AS t0 LEFT JOIN-Tabelle AS t1 ON t0.col1 = t1.col1 UND t0.col2 = t1.col2 UND (t1.col3, t1.pk)> (t0.col3, t0.pk) WO t1.col1 NULL IST;
Jon Armstrong - Xgc

2
Dies wäre besser lesbar alsSELECT 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

In MySQL gibt es keine Ranking-Funktionalität. Am nächsten können Sie eine Variable verwenden:

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

Wie würde das in meinem Fall funktionieren? Ich brauche zwei Variablen, eine für jede von col1 und col2? Col2 müsste irgendwie zurückgesetzt werden, wenn sich col1 ändert ..?

Ja. Wenn es Oracle wäre, könnten Sie die LEAD-Funktion verwenden, um den nächsten Wert zu erreichen. Zum Glück behandelt Quassnoi die Logik für das, was Sie in MySQL implementieren müssen .


1
Hmm ... wie würde das in meinem Fall funktionieren? Ich brauche zwei Variablen, eine für jede von col1 und col2? Col2 müsste irgendwie zurückgesetzt werden, wenn sich col1 ändert ..?
Paul

Danke ... wie ich oben sagte, diese Antwort wird von Bobince gleichermaßen akzeptiert, aber ich kann nur eine ankreuzen :-)
Paul

9
Das Zuweisen und Lesen von benutzerdefinierten Variablen in derselben Anweisung ist nicht zuverlässig. Dies ist hier dokumentiert: dev.mysql.com/doc/refman/5.0/de/user-variables.html : "In der Regel sollten Sie einer Benutzervariablen niemals einen Wert zuweisen und den Wert in derselben Anweisung lesen. Möglicherweise erhalten Sie die erwarteten Ergebnisse, dies ist jedoch nicht garantiert. Die Reihenfolge der Auswertung für Ausdrücke mit Benutzervariablen ist undefiniert und kann sich basierend auf den in einer bestimmten Anweisung enthaltenen Elementen ändern. "
Roland Bouman

1
@ Roland: Ich habe nur an kleinen Datensätzen getestet, hatte kein Problem. Schade, dass MySQL die Funktionalität noch nicht adressiert hat - die Anfrage ist seit 2008
eingegangen

2
Dies scheint ein undefiniertes Verhalten zu sein, wie Roland bemerkt. zB gibt dies völlig falsche Ergebnisse für eine Tabelle, die ich versucht habe: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

Am Ende folge ich immer diesem Muster. Angesichts dieser Tabelle:

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

Sie können dieses Ergebnis erhalten:

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

Durch Ausführen dieser Abfrage, für die keine Variable definiert werden muss:

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

Ich hoffe, das hilft!


1
Wenn Spalten VARCHAR oder CHAR sind, wie können Sie mit dieser Struktur damit umgehen?
Tushar

3
Du bist großartig Mosty, ich suche genau danach
Luckykrrish

Geben Sie diese Antwort einfach mit Ihrer Logik für row_number. Vielen Dank.
Utsav

@Tushar die Betreiber <, >, <=, >=handle CHAR und VARCHAR Datentypen auf alphabetischer Reihenfolge; Ich erwarte, ist genau das, wonach Sie suchen.
alex

1
@AlmazVildanov Sie sollten in der Lage sein, diese Abfrage einfach als Unterabfrage zum Herausfiltern zu verwenden. row_numbers <= 2 Und vielen Dank für diese Antwort Mosty, es ist perfekt!
Zax

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

1
Das erste: = scheint in der Antwort von @OMG Ponies zu fehlen. Vielen Dank für die Veröffentlichung dieses Peter Johnson.
Sholsinger

Ich denke (SELECT @i: = 0) AS foo sollte die erste Tabelle in der FROM-Anweisung sein, besonders wenn andere Tabellen Unterauswahl verwenden
andig

Warum brauchst du überhaupt das '.. as foo'?
Tom Chiverton

@ TomChiverton Wenn es fehlt, erhalten Sie: "Fehlercode: 1248. Jede abgeleitete Tabelle muss ihren eigenen Alias ​​haben"
ExStackChanger

1

27

In diesem Artikel wird gezeigt, wie SQL ROW_NUMBER () mit einer Partition in MySQL nachgeahmt wird. Ich bin in einer WordPress-Implementierung auf dasselbe Szenario gestoßen. Ich brauchte ROW_NUMBER () und es war nicht da.

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

Das Beispiel im Artikel verwendet eine einzelne Partition nach Feld. Um nach zusätzlichen Feldern zu partitionieren, können Sie Folgendes tun:

  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 

Die Verwendung von concat_ws behandelt Nullen. Ich habe dies anhand von 3 Feldern mit int, date und varchar getestet. Hoffe das hilft. Lesen Sie den Artikel, in dem diese Abfrage aufgeschlüsselt und erläutert wird.


1
Genial. Dies führt tatsächlich die Partitionierung durch. Sehr praktisch
Stuart Watt

1
Im Vergleich zu Self Join ist dies viel effizienter, aber es gibt ein Problem mit der Logik. Die Reihenfolge muss vor der Berechnung von row_num erfolgen. Concat ist ebenfalls nicht erforderlich. `` `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

Wenn Sie dies in eine Unterabfrage einfügen müssen, fügen Sie limit 18446744073709551615die Force- order byKlausel hinzu.
Xmedeko

concat_wsmit leerer Schnur ''ist gefährlich : concat_ws('',12,3) = concat_ws('',1,23). Verwenden Sie besser ein Trennzeichen '_'oder eine @ Kenneth Xu-Lösung.
Xmedeko

Die Verbindung von op ist tot; Archiv des Links hier
user2426679

25

Von MySQL 8.0.0und über können Sie nativ Fensterfunktionen verwenden.

1.4 Was ist neu in MySQL 8.0 :

Fensterfunktionen.

MySQL unterstützt jetzt Fensterfunktionen, die für jede Zeile aus einer Abfrage eine Berechnung mit Zeilen durchführen, die sich auf diese Zeile beziehen. Dazu gehören Funktionen wie RANK (), LAG () und NTILE (). Darüber hinaus können jetzt mehrere vorhandene Aggregatfunktionen als Fensterfunktionen verwendet werden. Zum Beispiel SUM () und AVG ().

ROW_NUMBER () over_clause :

Gibt die Nummer der aktuellen Zeile innerhalb der Partition zurück. Die Anzahl der Zeilen reicht von 1 bis zur Anzahl der Partitionszeilen.

ORDER BY wirkt sich auf die Reihenfolge aus, in der die Zeilen nummeriert sind. Ohne ORDER BY ist die Zeilennummerierung unbestimmt.

Demo:

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
seufz ... endlich!
Used_By_Already

15

Ich würde auch für Mosty Mostachos Lösung mit geringfügigen Änderungen an seinem Abfragecode stimmen:

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

Welches ergibt das gleiche Ergebnis:

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

für den Tisch:

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

Mit dem einzigen Unterschied, dass die Abfrage JOIN und GROUP BY nicht verwendet und stattdessen auf verschachtelte Auswahl setzt.


Soll das besser sein? Sie scheinen beide quadratisch zu sein, aber ich bin nicht sicher, wie ich die EXPLAIN-Ausgabe
interpretieren soll

Tatsächlich ist bekannt, dass verschachtelte Auswahlen in MySQL nicht sehr gut optimiert sind. Diese Antwort dient daher nur zur Demonstration einer Abfragetechnik. Die obigen variablenbasierten Beispiele funktionieren vermutlich in den meisten praktischen Fällen besser.
abcdn

1
Ich bin nicht davon überzeugt, dass eine der variablenbasierten Antworten tatsächlich ein definiertes Verhalten verwendet ...
jberryman

Es tut mir leid, ich bin nicht sicher, ob ich verstanden habe, was Sie unter "definiertem Verhalten" verstanden haben. Meinst du, es funktioniert bei dir nicht oder du machst dir nur Sorgen, dass es nicht dokumentiert ist?
abcdn

1
"Undefiniertes Verhalten" bedeutet, dass nicht dokumentiert ist, dass es funktioniert, und / oder dass nicht garantiert wird, dass es funktioniert. Siehe Zitate und Links zur Dokumentation in den Kommentaren auf dieser Seite. Es könnte zurückgeben, was man (unsund) will / errät / vermutet / phantasiert. Für bestimmte Versionen der Implementierung wurde gezeigt, dass bestimmte Abfrageausdrücke mit CASE-Inkrementierung und Verwendung von Variablen von Programmierern bei Percona anhand des Codes funktionieren. Das könnte sich mit jeder Veröffentlichung ändern.
Philipx

12

Ich würde eine Funktion definieren:

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

dann könnte ich tun:

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

Jetzt haben Sie keine Unterabfrage, die Sie in Ansichten nicht haben können.


Funktioniert mit einer Einschränkung: Wenn Sie die Abfrage mehrmals ausführen, erhalten Sie immer mehr fakeIds für dieselbe Ergebnismenge
Stephan Richter

Sie könnten set @fakeId = 0 senden; Jedes Mal, wenn Sie die Abfrage ausführen möchten, nicht optimal, aber funktioniert
jmpeace

Ein wirklich seltsames Problem tritt auf, wenn Sie DETERMINISTIC entfernen. Dann ist die fakeId falsch, wenn order by verwendet wird. Warum ist das?
Chris Muench

8

Abfrage nach Zeilennummer in MySQL

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

Dies kann bei UPDATE-Abfragen verwendet werden? Ich versuche es, aber ich erhalte den Fehler "Daten für Spalte abgeschnitten ...".
Diego

1
Wenn jemand daran interessiert ist, es in UPDATE zu verwenden, muss es als Unterabfrage verwendet werden, damit es funktioniert. UPDATE <Tabelle> SET <Feld> = (SELECT \ @row_number: = \ @row_number +1) ORDER BY <Ihre Bestellspalte>; Die Ordnungsspalte bestimmt die Wertreihenfolge der Zeilen.
Diego

8

Es gibt keine funtion wie rownum, row_num()in MySQL , aber die Art und Weise um ist wie folgt:

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

4

Die Lösung, die ich am besten fand, war die Verwendung einer Unterabfrage wie dieser:

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

Die PARTITION BY-Spalten werden nur mit '=' verglichen und durch AND getrennt. Die ORDER BY-Spalten werden mit '<' oder '>' verglichen und durch OR getrennt.

Ich habe festgestellt, dass dies sehr flexibel ist, auch wenn es ein bisschen teuer ist.


4

Die Rownumber-Funktionalität kann nicht nachgeahmt werden. Möglicherweise erhalten Sie die erwarteten Ergebnisse, aber höchstwahrscheinlich werden Sie irgendwann enttäuscht. In der MySQL-Dokumentation heißt es:

Bei anderen Anweisungen, wie z. B. SELECT, erhalten Sie möglicherweise die erwarteten Ergebnisse, dies ist jedoch nicht garantiert. In der folgenden Anweisung könnten Sie denken, dass MySQL zuerst @a auswertet und dann eine Zuweisung ausführt: SELECT @a, @a: = @ a + 1, ...; Die Reihenfolge der Auswertung für Ausdrücke mit Benutzervariablen ist jedoch undefiniert.

Grüße, Georgi.


Ich folge nicht. Wie ist "@i: = @i + 1 als Position" kein direkter Ersatz für "ROW_NUMBER () über (Reihenfolge nach Summe (Punktzahl) absteigend) als Position"?
Tom Chiverton

1
@ TomChiverton Weil sein Verhalten nicht definiert ist, wie das Handbuch genau dort sagt.
philipxy


2

Ich sehe keine einfache Antwort für den Teil "PARTITION BY". Hier ist meine:

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
  • Die ORDER BY-Klausel muss Ihren ROW_NUMBER-Bedarf widerspiegeln. Somit gibt es bereits eine klare Einschränkung: Sie können nicht mehrere ROW_NUMBER "Emulationen" dieses Formulars gleichzeitig haben.
  • Die Reihenfolge der "berechneten Spalte" ist wichtig . Wenn MySQL diese Spalte in einer anderen Reihenfolge berechnet, funktioniert dies möglicherweise nicht.
  • In diesem einfachen Beispiel habe ich nur einen eingefügt, aber Sie können mehrere "PARTITION BY" -Teile haben

        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

Ein bisschen spät, kann aber auch jemandem helfen, der nach Antworten sucht ...

Beispiel zwischen Zeilen / Zeilennummer - rekursive Abfrage, die in jedem SQL verwendet werden kann:

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
Entschuldigung, aber soweit ich weiß, unterstützt MySQL keine allgemeinen Tabellenausdrücke .
Álvaro González

es tut jetzt ... @ ÁlvaroGonzález MySQL 8 unterstützt nur CTE- und Fensterfunktionen, daher ist diese Antwort in älteren MySQL-Versionen nicht wirklich sinnvoll.
Raymond Nijland

1

Dies ermöglicht die gleiche Funktionalität, die ROW_NUMBER () AND PARTITION BY bietet, in 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

Auch ein bisschen spät, aber heute hatte ich das gleiche Bedürfnis, also suchte ich bei Google und fand schließlich einen einfachen allgemeinen Ansatz hier in Pinal Daves Artikel http://blog.sqlauthority.com/2014/03/09/mysql-reset-row -Nummer-für-jede-Gruppe-Partition-nach-Zeilennummer /

Ich wollte mich auf Pauls ursprüngliche Frage konzentrieren (das war auch mein Problem), also fasse ich meine Lösung als Arbeitsbeispiel zusammen.

Da wir über zwei Spalten partitionieren möchten, würde ich während der Iteration eine SET-Variable erstellen, um festzustellen, ob eine neue Gruppe gestartet wurde.

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

Die 3 bedeutet beim ersten Parameter von MAKE_SET, dass ich beide Werte im SET haben möchte (3 = 1 | 2). Wenn wir nicht zwei oder mehr Spalten haben, die die Gruppen bilden, können wir natürlich die Operation MAKE_SET eliminieren. Die Konstruktion ist genau die gleiche. Dies funktioniert bei mir nach Bedarf. Vielen Dank an Pinal Dave für seine klare Demonstration.


1
Beachten Sie, dass ORDER BYin einer Unterabfrage ignoriert werden kann (siehe mariadb.com/kb/en/mariadb/… ). Die vorgeschlagene Lösung besteht darin LIMIT 18446744073709551615, der Unterabfrage etwas hinzuzufügen, wodurch eine Sortierung erzwungen wird. Dies kann jedoch zu Leistungsproblemen führen und gilt nicht für wirklich verdammt große Tische :)
pnomolos

1

Dies könnte auch eine Lösung sein:

SET @row_number = 0;

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

Es macht jedoch keine Partitionierung und es unterscheidet sich nicht wesentlich von einer höher zitierten Antwort
Caius Jard

1

MySQL unterstützt ROW_NUMBER () seit Version 8.0+ .

Wenn Sie MySQL 8.0 oder höher verwenden, überprüfen Sie die Funktion ROW_NUMBER (). Andernfalls haben Sie die Funktion ROW_NUMBER () emuliert.

Die row_number () ist eine Rangfolgefunktion, die eine fortlaufende Nummer einer Zeile zurückgibt, beginnend mit 1 für die erste Zeile.

für ältere Version,

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

1

Wichtig: Bitte erwägen Sie ein Upgrade auf MySQL 8+ und verwenden Sie die definierte und dokumentierte Funktion ROW_NUMBER (). Lassen Sie alte Hacks hinter sich, die an eine funktionsbeschränkte alte Version von MySQL gebunden sind

Hier ist einer dieser Hacks:

Die Antworten hier, die meistens / alle Abfragevariablen verwenden, scheinen die Tatsache zu ignorieren, dass in der Dokumentation steht (Paraphrase):

Verlassen Sie sich nicht darauf, dass Elemente in der SELECT-Liste in der Reihenfolge von oben nach unten ausgewertet werden. Weisen Sie keine Variablen in einem SELECT-Element zu und verwenden Sie sie in einem anderen

Als solches besteht das Risiko, dass sie die falsche Antwort ausgeben, weil sie normalerweise eine Antwort geben

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

Wenn diese jemals von unten nach oben ausgewertet werden, funktioniert die Zeilennummer nicht mehr (keine Partitionen).

Wir müssen also etwas mit einer garantierten Ausführungsreihenfolge verwenden. Geben Sie CASE WHEN ein:

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

Als Gliederung ld ist die Reihenfolge der Zuweisung von prevcol wichtig - prevcol muss mit dem Wert der aktuellen Zeile verglichen werden, bevor wir ihm einen Wert aus der aktuellen Zeile zuweisen (andernfalls wäre es der Spaltenwert der aktuellen Zeilen, nicht der Spaltenwert der vorherigen Zeile). .

So passt das zusammen:

  • Das erste WANN wird ausgewertet. Wenn die Spalte dieser Zeile mit der Spalte der vorherigen Zeile übereinstimmt, wird @r inkrementiert und vom CASE zurückgegeben. Diese LED-Rückgabewerte werden in @r gespeichert. Es ist eine Funktion von MySQL, dass die Zuweisung den neuen Wert dessen, was in @r zugewiesen ist, in die Ergebniszeilen zurückgibt.

  • Für die erste Zeile in der Ergebnismenge ist @prevcol null (es wird in der Unterabfrage auf null initialisiert), daher ist dieses Prädikat falsch. Dieses erste Prädikat gibt bei jeder Änderung der Spalte auch false zurück (die aktuelle Zeile unterscheidet sich von der vorherigen Zeile). Dies bewirkt, dass das zweite WANN ausgewertet wird.

  • Das zweite WHEN-Prädikat ist immer falsch und dient lediglich dazu, @prevcol einen neuen Wert zuzuweisen. Da sich die Spalte dieser Zeile von der Spalte der vorherigen Zeile unterscheidet (wir wissen dies, weil bei gleicher Verwendung das erste WANN verwendet worden wäre), müssen wir den neuen Wert zuweisen, damit er beim nächsten Test beibehalten wird. Da die Zuweisung vorgenommen wird und dann das Ergebnis der Zuweisung mit null verglichen wird und alles, was mit null gleichgesetzt wird, falsch ist, ist dieses Prädikat immer falsch. Zumindest die Auswertung hat jedoch dazu beigetragen, den Wert von col aus dieser Zeile beizubehalten, sodass er anhand des col-Werts der nächsten Zeile ausgewertet werden kann

  • Da das zweite WHEN falsch ist, bedeutet dies, dass in Situationen, in denen sich die Spalte, nach der wir partitionieren (col), geändert hat, ELSE einen neuen Wert für @r angibt und die Nummerierung von 1 neu startet

Wir kommen zu einer Situation, in der dies:

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

Hat die allgemeine Form:

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

Fußnoten:

  • Das p in pcol bedeutet "Partition", das o in ocol bedeutet "Reihenfolge" - in der allgemeinen Form habe ich das "prev" aus dem Variablennamen entfernt, um die visuelle Unordnung zu verringern

  • Die Klammern (@pcolX := colX) = nullsind wichtig. Ohne sie weisen Sie @pcolX null zu und die Dinge funktionieren nicht mehr

  • Es ist ein Kompromiss, dass die Ergebnismenge auch nach den Partitionsspalten sortiert werden muss, damit der Vergleich der vorherigen Spalte funktioniert. Sie können Ihre Rownumber daher nicht nach einer Spalte sortieren lassen, sondern Ihre Ergebnismenge nach einer anderen. Möglicherweise können Sie dies mit Unterabfragen beheben. Ich glaube jedoch, dass in den Dokumenten auch angegeben ist, dass die Reihenfolge der Unterabfragen ignoriert werden kann, sofern nicht LIMIT verwendet wird und dies Auswirkungen haben könnte Performance

  • Ich habe mich nicht weiter damit befasst, als zu testen, ob die Methode funktioniert, aber wenn das Risiko besteht, dass die Prädikate im zweiten WANN wegoptimiert werden (alles im Vergleich zu null ist null / falsch, warum also die Aufgabe ausführen) und nicht ausgeführt werden hört es auch auf. Dies scheint meiner Erfahrung nach nicht zu geschehen, aber ich nehme gerne Kommentare an und schlage eine Lösung vor, wenn dies vernünftigerweise möglich ist

  • Es kann sinnvoll sein, die Nullen, mit denen @pcolX erstellt wird, in die tatsächlichen Typen Ihrer Spalten in der Unterabfrage umzuwandeln, mit der die Variablen @pcolX erstellt werden. select @pcol1 := CAST(null as INT), @pcol2 := CAST(null as DATE)


Dafür gibt es keine Rechtfertigung. Genau wie die anderen Antworten, die derselben Variablen zugewiesen und aus derselben gelesen werden.
Philipip

Können Sie mehr Details Phil liefern?
Caius Jard

Siehe meine anderen Kommentare auf dieser Seite. Googeln 'Site: stackoverflow.com "philipxy" MySQL-Variable (setzen ODER zuweisen ODER zuweisen ODER schreiben) lesen': Eine Antwort von mir & ein Fehlerbericht, der in einem Kommentar von mir zu dieser Frage verlinkt ist , wobei die akzeptierte Antwort das Handbuch noch sofort zitiert in Behauptungen ist es in Ordnung, etwas im Widerspruch dazu zu tun. Lesen Sie das Handbuch zu Variablen und Zuordnung.
philipxy


Ich verstehe Ihre Besorgnis
Caius Jard

0

Dies ist nicht die robusteste Lösung. Wenn Sie jedoch nur einen partitionierten Rang in einem Feld mit nur wenigen unterschiedlichen Werten erstellen möchten, ist es möglicherweise nicht unhandlich, einen Fall zu verwenden, wenn Logik mit so vielen Variablen wie erforderlich benötigt wird.

So etwas hat in der Vergangenheit bei mir funktioniert:

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

Hoffe das macht Sinn / hilft!


-1

Dies funktioniert perfekt für mich, um RowNumber zu erstellen, wenn wir mehr als eine Spalte haben. In diesem Fall zwei Spalten.

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
Bitte versuchen Sie, Antworten zu formatieren, und geben Sie einen zusätzlichen Kontext an, was Sie tun möchten. Im Moment ist es nichts weiter als schlecht formatierter Text.
Yannick Meeus

2
Dies scheint keine Beziehung zur ursprünglichen Frage zu haben. Wenn Sie eine eigene Frage haben, stellen Sie diese bitte separat.
Jeroen Mostert

-5
SELECT 
    col1, col2, 
    count(*) as intRow
FROM Table1
GROUP BY col1,col2
ORDER BY col3 desc
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.