it-swarm.com.de

ROW_NUMBER () in MySQL

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

Zum Beispiel:

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, um intRow auf 1 zu begrenzen, um eine einzelne Zeile mit dem höchsten col3 für jedes (col1, col2) -Paar zu erhalten.

263
Paul

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

Das ist ein maximal gruppenweise , eine der am häufigsten gestellten SQL-Fragen (da es so scheint, als ob es einfach sein sollte, aber eigentlich ist es das 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 übereinstimmender Spalte 1, Spalte 2 eine höhere Spalte 3 hat." , col3. Wenn das ein Problem ist, müssen Sie möglicherweise nachbearbeiten.)

97
bobince

In MySQL gibt es keine Ranglistenfunktion. Am ehesten 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 würde zwei Variablen benötigen, eine für jede der Spalten 1 und 2? Col2 müsste irgendwie zurückgesetzt werden, wenn col1 geändert wird ..?

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 .

197
OMG Ponies

Ich folge 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!

80
Mosty Mostacho
SELECT 
    @i:[email protected]+1 AS iterator, 
    t.*
FROM 
    tablename AS t,
    (SELECT @i:=0) AS foo
58
Peter Johnson

In diesem Artikel wird gezeigt, wie SQL ROW_NUMBER () mit einer Partition von in MySQL nachgebildet wird. Ich bin in einer WordPress -Implementierung auf genau 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 einem int, date und varchar getestet. Hoffe das hilft. Lesen Sie den Artikel, der diese Abfrage aufschlüsselt und erläutert.

27
birch

Ab MySQL 8.0.0 können Sie Fensterfunktionen verwenden.

1.4 Was ist neu in MySQL 8. :

Fensterfunktionen.

MySQL unterstützt jetzt Fensterfunktionen, die für jede Zeile einer Abfrage eine Berechnung mit Zeilen ausführen, die sich auf diese Zeile beziehen. Dazu gehören Funktionen wie RANK (), LAG () und NTILE (). Außerdem 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 in ihrer Partition zurück. Die Zeilennummern reichen 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

21
Lukasz Szozda

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 wird das gleiche Ergebnis geben:

+------+------+------------+
|    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 geschachtelte Auswahl verwendet.

15
abcdn

Ich würde eine Funktion definieren:

delimiter $$
DROP FUNCTION IF EXISTS `getFakeId`$$
CREATE FUNCTION `getFakeId`() RETURNS int(11)
    DETERMINISTIC
begin
return if(@fakeId, @fakeId:[email protected]+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 nicht in Ansichten haben können.

11
Quincy

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

select 
      @s:[email protected]+1 serial_no, 
      tbl.* 
from my_table tbl, (select @s:=0) as s;
8
Md. Kamruzzaman

abfrage für row_number in MySQL

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

Die Rownumber-Funktionalität kann nicht nachgeahmt werden. Sie erhalten möglicherweise die Ergebnisse, die Sie erwarten, aber höchstwahrscheinlich werden Sie irgendwann enttäuscht. Hier ist, was MySQL-Dokumentation sagt:

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

Grüße, Georgi.

4
user3503199

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

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
snydergd

MariaDB 10.2 implementiert "Fensterfunktionen", darunter RANK (), ROW_NUMBER () und einige andere Dinge:

https://mariadb.com/kb/en/mariadb/window-functions/

Basierend auf einem Vortrag bei Percona Live in diesem Monat, sind sie ziemlich gut optimiert.

Die Syntax ist identisch mit dem Code in der Frage.

3
Rick James

Ich sehe keine einfache Antwort für den Teil "PARTITION BY", also hier ist meine:

SELECT
    *
FROM (
    select
        CASE WHEN @partitionBy_1 = l THEN @row_number:[email protected]_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" angelegenheiten. Wenn Sie mysql veranlassen, diese Spalten in einer anderen Reihenfolge zu berechnen, funktioniert dies möglicherweise nicht.
  • In diesem einfachen Beispiel habe ich nur einen angegeben, aber Sie können mehrere "PARTITION BY" -Teile haben

        CASE WHEN @partitionBy_1 = part1 AND @partitionBy_2 = part2 [...] THEN @row_number:[email protected]_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
    

Auch ein bisschen spät, aber heute hatte ich das gleiche Bedürfnis, also habe ich auf Google gesucht und schließlich einen einfachen allgemeinen Ansatz hier in Pinal Daves Artikel gefunden http://blog.sqlauthority.com/2014/03/09/mysql -Reset-Zeilennummer-für-jede-Gruppenpartition-nach-Zeilennummer /

Ich wollte mich auf die ursprüngliche Frage von Paul konzentrieren (das war auch mein Problem) und fasse meine Lösung als funktionierendes Beispiel 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 will (3 = 1 | 2). Wenn wir nicht zwei oder mehr Spalten haben, die die Gruppen bilden, können wir natürlich die MAKE_SET-Operation eliminieren. Der Aufbau ist genauso. Das funktioniert bei mir nach Bedarf. Vielen Dank an Pinal Dave für seine klare Demonstration.

1
Miklos Krivan

Dies könnte auch eine Lösung sein:

SET @row_number = 0;

SELECT 
    (@row_number:[email protected]_number + 1) AS num, firstName, lastName
FROM
    employees
1
Rishabh Pandey

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

Wenn Sie MySQL 8.0 oder höher verwenden, lesen Sie die Funktion ROW_NUMBER (). Andernfalls müssen Sie die Funktion ROW_NUMBER () emulieren.

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;

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

Beispiel für eine rekursive Abfrage zwischen Zeilen/Zeilennummer, 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
1
Art

Dies ermöglicht die gleiche Funktionalität wie ROW_NUMBER () UND PARTITION BY 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
Alankar

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

Hier ist einer dieser Hacks:

Die Antworten hier, die In-Query-Variablen verwenden, scheinen die Tatsache, dass in der Dokumentation (Umschreibung) steht, größtenteils zu ignorieren:

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

Als solches besteht die Gefahr, dass sie die falsche Antwort abgeben, weil sie in der Regel a tun

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 ein, wenn:

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

Wie in Umriss ld angegeben, 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 aktuelle Spaltenwert, 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 zurückgegebenen LED-Werte 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 (in der Unterabfrage ist es auf null initialisiert), sodass dieses Prädikat falsch ist. Dieses erste Prädikat gibt auch jedes Mal false zurück, wenn sich die Spalte ändert (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 (dies ist bekannt, da bei einer identischen Spalte das erste WANN verwendet worden wäre), müssen wir den neuen Wert zuweisen, um ihn für den nächsten Test beizubehalten. Da die Zuweisung erfolgt und 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 den Wert von col aus dieser Zeile beibehalten, sodass er mit dem col-Wert der nächsten Zeile verglichen werden kann

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

Wir kommen zu einer Situation, in der:

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 "order" - in der allgemeinen Form habe ich das "prev" aus dem Variablennamen entfernt, um die visuelle Unordnung zu verringern

  • Die Klammern um (@pcolX := colX) = null sind wichtig. Ohne sie weisen Sie @pcolX den Wert null zu, und es funktioniert nicht mehr

  • Es ist ein Kompromiss, dass die Ergebnismenge auch nach Partitionsspalten sortiert werden muss, damit der Vergleich der vorherigen Spalte funktioniert. Sie können daher Ihre Rownummer nicht nach einer Spalte, sondern nach einer anderen nach einer Ergebnismenge ordnen. Möglicherweise können Sie dies mit Unterabfragen beheben. Ich bin jedoch der Ansicht, dass in den Dokumenten auch angegeben ist, dass die Reihenfolge von Unterabfragen ignoriert werden kann, es sei denn, LIMIT wird verwendet und dies könnte sich auswirken Performance

  • Ich habe mich nicht weiter damit beschäftigt, als zu testen, ob die Methode funktioniert, aber wenn das Risiko besteht, dass die Prädikate im zweiten WANN wegoptimiert werden (alles, was mit null verglichen wird, ist null/falsch, warum also die Aufgabe ausführen?) Und nicht ausgeführt es hört auch auf. Dies scheint nach meiner Erfahrung nicht zu geschehen, aber ich werde gerne Kommentare annehmen und eine Lösung vorschlagen, wenn dies vernünftigerweise eintreten könnte

  • Es kann sinnvoll sein, die Nullen, die @pcolX erzeugen, in die tatsächlichen Typen Ihrer Spalten in der Unterabfrage umzuwandeln, die die @pcolX-Variablen erzeugt, nämlich: select @pcol1 := CAST(null as INT), @pcol2 := CAST(null as DATE)

0
Caius Jard