ROW_NUMBER () w MySQL


281

Czy istnieje dobry sposób na replikację funkcji SQL Server w MySQL ROW_NUMBER()?

Na przykład:

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

Następnie mógłbym na przykład dodać warunek ograniczenia intRowdo 1, aby uzyskać pojedynczy wiersz z najwyższym col3dla każdej (col1, col2)pary.


9
Tagged with, greatest-n-per-groupaby poprowadzić Cię do podobnych pytań.
Bill Karwin,

1
Usunięto Sql-Serverznacznik, ponieważ był to najwyżej oceniany element w łączonym wyszukiwaniu znaczników, ale tak naprawdę nie dotyczy SQL Server.
Martin Smith,

dla prostej funkcji numeru wiersza mysql, sprawdź datamakessense.com/mysql-rownum-row-number-function
AdrianBR

W przypadku MySQL jedyne wydajne rozwiązania zostały omówione w mysql.rjweb.org/doc.php/groupwise_max . Wiele opublikowanych rozwiązań wymaga pełnego skanowania tabeli (lub gorzej).
Rick James,

2
Bezwstydnie autopromocyjne rozwiązanie dla prawdziwej funkcji analitycznej ROW_NUMBER, RANK, DESNSE_RANK w MySQL
Kenneth Xu

Odpowiedzi:


102

Chcę wiersz z pojedynczym najwyższym col3 dla każdej pary (col1, col2).

Jest to maksimum grupowe , jedno z najczęściej zadawanych pytań SQL (ponieważ wydaje się, że powinno to być łatwe, ale tak naprawdę nie jest).

Często staram się o samodzielne dołączenie:

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;

„Pobierz wiersze w tabeli, dla których żaden inny wiersz z pasującym col1, col2 nie ma wyższego col3”. (Zauważysz to i większość innych grupowo maksymalnych rozwiązań zwróci wiele wierszy, jeśli więcej niż jeden wiersz ma ten sam col1, col2, col3. Jeśli to jest problem, możesz potrzebować dodatkowej obróbki).


2
Ale co, jeśli istnieją dwie maksymalne wartości col3 dla pary (col1, col2)? Skończysz z dwoma rzędami.
Paul

@Paul: tak! Właśnie dodałem notatkę o tym w odpowiedzi tik temu. Zwykle możesz z łatwością losowo usunąć niechciane dodatkowe wiersze w warstwie aplikacji, ale jeśli masz wiele wierszy z tym samym kol3, może to być problematyczne.
bobince

1
Bobin, rozwiązanie stało się dość popularne tutaj na SO, ale mam pytanie. Rozwiązanie jest w zasadzie takie samo, jakby ktoś próbował znaleźć największy identyfikator za pomocą następującego zapytania: SELECT t1.id FROM test t1 LEFT JOIN test t2 ON t1.id>t2.id WHERE t2.id IS NULL;Czy nie wymaga n*n/2 + n/2porównania IS NULL, aby znaleźć pojedynczy wiersz? Czy zdarzają się jakieś optymalizacje, których nie widzę? Próbowałem zadać podobne pytanie do Billa w innym wątku, ale on chyba to zignorował.
newtover

2
@Paul - Aby rozwiązać przypadek, w którym istnieje wiele wierszy pasujących do maksimum na grupę i chcesz pobrać tylko jeden, zawsze możesz dodać klucz podstawowy w logice klauzuli ON, aby zerwać powiązanie ... WYBIERZ t0.col3 Z tabeli AS t0 LEWA JOIN tabela AS t1 ON t0.col1 = t1.col1 AND t0.col2 = t1.col2 AND (t1.col3, t1.pk)> (t0.col3, t0.pk) GDZIE t1.col1 JEST NULL;
Jon Armstrong - Xgc

2
Byłoby to bardziej czytelne jakoSELECT 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

W MySQL nie ma funkcji rankingu. Najbliższe, jakie możesz uzyskać, to użyć zmiennej:

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

więc jak to by działało w moim przypadku? Potrzebowałbym dwóch zmiennych, po jednej dla każdej z kolumn col1 i col2? Col2 będzie musiał jakoś zresetować po zmianie col1 ..?

Tak. Gdyby to była Oracle, można użyć funkcji LEAD, aby osiągnąć szczyt przy następnej wartości. Na szczęście Quassnoi obejmuje logikę tego, co należy zaimplementować w MySQL .


1
Hmm .... więc jak to by zadziałało w moim przypadku? Potrzebowałbym dwóch zmiennych, po jednej dla każdej z kolumn col1 i col2? Col2 będzie musiał jakoś zresetować po zmianie col1 ..?
Paul

Dzięki ... jak powiedziałem powyżej, ta odpowiedź jest równie akceptowana przez bobince, ale mogę tylko zaznaczyć jedną :-)
Paul

9
Przypisywanie i odczytywanie zmiennych zdefiniowanych przez użytkownika w tej samej instrukcji nie jest wiarygodne. jest to udokumentowane tutaj: dev.mysql.com/doc/refman/5.0/en/user-variables.html : „Zasadniczo nigdy nie należy przypisywać wartości do zmiennej użytkownika i odczytywać wartości w obrębie tej samej instrukcji. Możesz uzyskać oczekiwane wyniki, ale nie jest to gwarantowane. Kolejność obliczania wyrażeń zawierających zmienne użytkownika jest niezdefiniowana i może się zmieniać w zależności od elementów zawartych w danej instrukcji. ”
Roland Bouman,

1
@Roland: Testowałem tylko na małych zestawach danych, nie miałem żadnych problemów. Szkoda, że ​​MySQL jeszcze nie zajął się tą funkcjonalnością - prośba została
wysłana

2
To wydaje się być niezdefiniowanym zachowaniem, jak zauważa Roland. np. daje to zupełnie niepoprawne wyniki dla tabeli, którą próbowałem: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 26.04.17

81

Zawsze kończę według tego wzoru. Biorąc pod uwagę tę tabelę:

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

Możesz uzyskać ten wynik:

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

Uruchamiając to zapytanie, które nie wymaga żadnej zdefiniowanej zmiennej:

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

Mam nadzieję, że to pomaga!


1
jeśli kolumny to VARCHAR lub CHAR, jak sobie z tym poradzić dzięki tej strukturze?
Tushar

3
Jesteś niesamowity Mosty, właśnie tego
szukam

Właśnie podałem tę odpowiedź, używając logiki dla wiersza numer_wiersza. Dzięki.
Utsav

@Tushar operatorów <, >, <=, >=uchwyt CHAR i VARCHAR typy danych w kolejności alfabetycznej; Oczekuję, że jest dokładnie tym, czego szukasz.
alex

1
@AlmazVildanov powinieneś móc użyć tego zapytania jako subquery do odfiltrowania row_numbers <= 2 I wielkie dzięki za tę odpowiedź Mosty, jest idealny!
Zax,

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

1
Pierwszy: wydaje się, że brakuje odpowiedzi @OMG Ponies. Dzięki za opublikowanie tego Peter Johnson.
sholsinger

Myślę, że (SELECT @i: = 0) AS foo powinna być pierwszą tabelą w instrukcji FROM, szczególnie jeśli inne tabele używają
podselekcji

Dlaczego potrzebujesz nawet „.. as foo”?
Tom Chiverton

@TomChiverton Jeśli go brakuje, otrzymasz: „Kod błędu: 1248. Każda tabela pochodna musi mieć swój własny alias”
ExStackChanger

1
Przypisanie rangi tutaj jest całkowicie niezdefiniowane, a to nawet nie odpowiada na pytanie
jberryman

27

Sprawdź ten artykuł, pokazuje on, jak naśladować SQL ROW_NUMBER () za pomocą partycji w MySQL. Natknąłem się na ten sam scenariusz w implementacji WordPress. Potrzebowałem ROW_NUMBER () i nie było go.

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

Przykładem w tym artykule jest użycie pojedynczej partycji według pól. Aby podzielić na dodatkowe pola, możesz zrobić coś takiego:

  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 

Korzystanie z concat_ws obsługuje wartości null. Przetestowałem to na 3 polach, używając int, date i varchar. Mam nadzieję że to pomoże. Sprawdź artykuł, w którym łamie to zapytanie i wyjaśnia je.


1
Niesamowite. To faktycznie robi partycjonowanie. Bardzo przydatny
Stuart Watt,

1
W porównaniu do samodzielnego łączenia, jest to o wiele bardziej wydajne, ale istnieje problem z logiką, kolejność musi wystąpić przed obliczeniem numer_wiersza, konkat również nie jest konieczny. `` 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

Jeśli potrzebujesz tu umieścić to w podzapytaniu, dodaj klauzulę limit 18446744073709551615force order by.
xmedeko,

concat_wsz pusty ciąg ''jest niebezpieczna concat_ws('',12,3) = concat_ws('',1,23). Lepiej użyć separatora '_'lub rozwiązania @Kenneth Xu.
xmedeko,

link operacji nie działa; archiwum linku tutaj
user2426679,

25

Od MySQL 8.0.0i powyżej można natywnie używać funkcji okienkowych.

1.4 Co nowego w MySQL 8.0 :

Funkcje okna

MySQL obsługuje teraz funkcje okien, które dla każdego wiersza zapytania wykonują obliczenia przy użyciu wierszy powiązanych z tym wierszem. Należą do nich takie funkcje, jak RANK (), LAG () i NTILE (). Ponadto kilka istniejących funkcji agregujących może być teraz używanych jako funkcje okien; na przykład SUM () i AVG ().

ROW_NUMBER () over_clause :

Zwraca numer bieżącego wiersza w obrębie jego partycji. Liczba wierszy wynosi od 1 do liczby rzędów partycji.

ORDER BY wpływa na kolejność numerowania wierszy. Bez ORDER BY numeracja wierszy jest nieokreślona.

Próbny:

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
westchnienie ... w końcu!
Used_By_Already

15

Głosowałbym również za rozwiązaniem Mosty Mostacho z niewielkimi modyfikacjami jego kodu zapytania:

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

Co da ten sam wynik:

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

dla stołu:

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

Jedyną różnicą jest to, że zapytanie nie używa opcji ŁĄCZ i GRUPUJ WEDŁUG, zamiast tego korzysta z zagnieżdżonego wyboru.


Czy to ma być lepsze? Oba wydają się być kwadratowe, ale nie jestem pewien, jak interpretować wynik EXPLAIN
jberryman

W rzeczywistości zagnieżdżone selekcje nie są zbyt dobrze zoptymalizowane w MySQL, więc ta odpowiedź służy tylko do demonstracji techniki zapytań. Sądzę, że powyższe przykłady oparte na zmiennych działają lepiej w większości praktycznych przypadków.
abcdn

1
Nie jestem przekonany, że żadna z odpowiedzi opartych na zmiennych faktycznie używa określonego zachowania ...
jberryman

Przepraszam, nie jestem pewien, czy rozumiem, co rozumiesz przez „określone zachowanie”. Czy masz na myśli, że to nie działa, czy martwisz się, że nie jest to udokumentowane?
abcdn

1
„Nieokreślone zachowanie” oznacza, że ​​nie jest udokumentowane do pracy i / lub udokumentowane, że nie gwarantuje się pracy. Zobacz cytaty i linki do dokumentacji w komentarzach na tej stronie. To może powrócić czego jeden (Nierozsądna) chce / domysły / hipotezę / fantazjuje. W przypadku niektórych wersji implementacji niektóre wyrażenia zapytania korzystające ze zwiększania CASE i używania zmiennych okazały się działać przez programistów w Perconie, patrząc na kod. To może się zmienić w każdej wersji.
philipxy

12

Zdefiniowałbym funkcję:

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

wtedy mógłbym zrobić:

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

Teraz nie masz podzapytania, którego nie możesz mieć w widokach.


Działa z jednym ograniczeniem: jeśli wykonasz zapytanie kilka razy, otrzymasz coraz więcej fałszywych identyfikatorów dla tego samego zestawu wyników
Stephan Richter

możesz wysłać zestaw @fakeId = 0; za każdym razem, gdy chcesz uruchomić zapytanie, nie jest optymalne, ale działa
jmpeace

Naprawdę dziwny problem zdarza się, jeśli usuniesz DETERMINISTIC. Zatem fakeId jest nieprawidłowy, gdy używasz kolejności według. Dlaczego to?
Chris Muench

8

zapytanie o numer_wiersza w mysql

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

Można tego użyć w zapytaniach UPDATE? Próbuję, ale pojawia się błąd „dane obcięte dla kolumny ...”.
Diego

1
Jeśli ktoś jest zainteresowany użyciem go w AKTUALIZACJI, należy go użyć jako zapytania podrzędnego, aby działał. AKTUALIZACJA <tabela> USTAW <pole> = (WYBIERZ \ numer_wiersza: = \ @ numer_row +1) ZAMÓWIENIE <kolumna zamówienia>; Kolumna zamówienia określa kolejność wartości wierszy.
Diego

8

W MySQL nie ma takiej funkcji rownum, row_num()ale sposób postępowania jest następujący:

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

4

Rozwiązaniem, które okazało się najlepsze, było użycie takiego podzapytania:

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

Kolumny PARTITION BY po prostu porównuje się z „=” i oddziela je AND. Kolumny ORDER BY byłyby porównywane z „<” lub „>” i oddzielone OR.

Uważam, że jest to bardzo elastyczne, nawet jeśli jest trochę kosztowne.


4

Funkcji numeru początkowego nie można naśladować. Możesz uzyskać oczekiwane wyniki, ale na pewnym etapie najprawdopodobniej będziesz rozczarowany. Oto, co mówi dokumentacja mysql:

W przypadku innych instrukcji, takich jak SELECT, możesz uzyskać oczekiwane wyniki, ale nie jest to gwarantowane. W poniższej instrukcji możesz pomyśleć, że MySQL najpierw oceni @a, a następnie wykona zadanie: SELECT @a, @a: = @ a + 1, ...; Jednak kolejność obliczania wyrażeń zawierających zmienne użytkownika jest niezdefiniowana.

Pozdrawiam, Georgi.


Nie podążam. W jaki sposób „@i: = @i + 1 jako pozycja” nie zastępuje bezpośrednio „ROW_NUMBER () ponad (kolejność według sumy (wynik) desc) jako pozycja”)?
Tom Chiverton

1
@TomChiverton Ponieważ jego zachowanie nie jest zdefiniowane, jak mówi instrukcja w tym miejscu.
philipxy


2

Nie widzę żadnej prostej odpowiedzi dotyczącej części „PARTITION BY”, więc oto moja:

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
  • Klauzula ORDER BY musi odzwierciedlać twoją potrzebę ROW_NUMBER. Zatem istnieje już wyraźne ograniczenie: nie można jednocześnie mieć kilku emulacji ROW_NUMBER tej formy.
  • Kolejność „kolumny obliczeniowej” ma znaczenie . Jeśli mysql obliczy te kolumny w innej kolejności, może nie działać.
  • W tym prostym przykładzie podałem tylko jedną, ale możesz mieć kilka części „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

Trochę późno, ale może też pomóc komuś, kto szuka odpowiedzi ...

Przykład wierszy / numer_wiersza - zapytanie rekurencyjne, które może być użyte w dowolnym 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
Przykro mi, ale o ile wiem MySQL nie obsługuje typowych wyrażeń tabelowych .
Álvaro González

robi to teraz ... @ ÁlvaroGonzález MySQL 8 obsługuje tylko funkcje CTE i okna, więc ta odpowiedź nie ma sensu używać w starszych wersjach MySQL.
Raymond Nijland

1

Pozwala to uzyskać taką samą funkcjonalność, jaką zapewnia ROW_NUMBER () AND PARTITION BY w 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

Trochę za późno, ale dzisiaj miałem tę samą potrzebę, więc przeszukałem Google i wreszcie znalazłem proste ogólne podejście tutaj w artykule Pinal Dave http://blog.sqlauthority.com/2014/03/09/mysql-reset-row -numer-for-each-group-partition-by-row-number /

Chciałem skoncentrować się na pierwotnym pytaniu Paula (to też był mój problem), dlatego podsumowałem moje rozwiązanie jako działający przykład.

Ponieważ chcemy podzielić na dwie kolumny, podczas iteracji utworzę zmienną SET, aby sprawdzić, czy nowa grupa została uruchomiona.

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

3 oznacza przy pierwszym parametrze MAKE_SET, że chcę obie wartości w SET (3 = 1 | 2). Oczywiście, jeśli nie mamy dwóch lub więcej kolumn konstruujących grupy, możemy wyeliminować operację MAKE_SET. Konstrukcja jest dokładnie taka sama. To działa dla mnie zgodnie z wymaganiami. Ogromne podziękowania dla Pinal Dave za jego wyraźną demonstrację.


1
Zauważ, że ORDER BYw podzapytaniu można zignorować (patrz mariadb.com/kb/en/mariadb/… ). Sugerowanym rozwiązaniem tego jest dodanie LIMIT 18446744073709551615do podzapytania, które wymusza sortowanie. Może to jednak powodować problemy z wydajnością i nie dotyczy naprawdę cholernych wielkich tabel :)
pnomolos

1

Może to być również rozwiązanie:

SET @row_number = 0;

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

Nie dzieli jednak na partycje i nie różni się znacząco od wyżej cytowanej odpowiedzi
Caius Jard

1

MySQL obsługuje ROW_NUMBER () od wersji 8.0+ .

Jeśli używasz MySQL w wersji 8.0 lub nowszej, sprawdź funkcję ROW_NUMBER (). W przeciwnym razie emulujesz funkcję ROW_NUMBER ().

Row_number () to funkcja rankingu, która zwraca kolejny numer wiersza, zaczynając od 1 dla pierwszego wiersza.

dla starszej wersji

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

1

Ważne: Zastanów się nad aktualizacją do MySQL 8+ i użyj zdefiniowanej i udokumentowanej funkcji ROW_NUMBER () oraz porzuć stare hacki powiązane z ograniczoną funkcjonalnością starą wersją MySQL

Oto jeden z tych hacków:

Odpowiedzi tutaj, które wykorzystują zmienne w zapytaniu głównie / wszystkie wydają się ignorować fakt, że dokumentacja mówi (parafraza):

Nie polegaj na tym, że elementy na liście SELECT są oceniane w kolejności od góry do dołu. Nie przypisuj zmiennych do jednego elementu SELECT i nie używaj ich w innym

Jako takie istnieje ryzyko, że wyrzucą złą odpowiedź, ponieważ zazwyczaj robią to

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

Jeśli zostaną one kiedykolwiek ocenione oddolnie, numer wiersza przestanie działać (brak partycji)

Musimy więc użyć czegoś z gwarantowaną kolejnością wykonania. Wpisz PRZYPADEK, GDY:

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

Jak zarys ld, kolejność przypisywania prevcol jest ważna - prevcol należy porównać z wartością bieżącego wiersza, zanim przypiszemy mu wartość z bieżącego wiersza (w przeciwnym razie byłaby to wartość aktualnego wiersza col, a nie wartość col poprzedniego wiersza) .

Oto jak to pasuje do siebie:

  • Pierwszy KIEDY jest oceniany. Jeśli kolumna tego wiersza jest taka sama jak kolumna poprzedniego wiersza, @r jest zwiększane i zwracane z CASE. Te zwracane wartości są przechowywane w @r. Cechą MySQL jest to, że przypisanie zwraca nową wartość tego, co jest przypisane do @r, w wierszach wyników.

  • Dla pierwszego wiersza w zestawie wyników @prevcol ma wartość null (jest inicjowane do null w podzapytaniu), więc ten predykat ma wartość false. Ten pierwszy predykat zwraca również wartość false przy każdej zmianie kolumny (bieżący wiersz różni się od poprzedniego). Powoduje to ocenę drugiego KIEDY.

  • Drugi predykat KIEDY jest zawsze fałszywy i istnieje wyłącznie w celu przypisania nowej wartości @prevcol. Ponieważ kolumna tego wiersza jest inna niż kolumna poprzedniego wiersza (wiemy o tym, ponieważ gdyby był taki sam, użyłby pierwszego KIEDY), musimy przypisać nową wartość, aby zachować ją do następnego testu. Ponieważ przypisanie jest wykonane, a następnie wynik przypisania jest porównywany z wartością NULL, a wszystko, co jest utożsamiane z wartością NULL, jest fałszywe, predykat ten jest zawsze fałszywy. Ale przynajmniej jego ocena polegała na zachowaniu wartości col z tego wiersza, dzięki czemu można ją oszacować na podstawie wartości col następnego rzędu

  • Ponieważ drugie KIEDY jest fałszem, oznacza to, że w sytuacjach, w których kolumna, którą dzielimy według (col) uległa zmianie, to ELSE daje nową wartość @r, restartując numerację od 1

Dochodzimy do sytuacji, w której:

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

Ma ogólną 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

Przypisy:

  • P in pcol oznacza „partycja”, o in ocol oznacza „porządek” - w ogólnej formie usunąłem „poprzednią” z nazwy zmiennej, aby zmniejszyć bałagan wizualny

  • Nawiasy kwadratowe (@pcolX := colX) = nullsą ważne. Bez nich przypisz null do @pcolX i rzeczy przestaną działać

  • Kompromisem jest to, że zestaw wyników musi być także uporządkowany według kolumn partycji, aby porównanie z poprzednią kolumną się sprawdziło. Nie można w ten sposób uporządkować numeru początkowego według jednej kolumny, ale zestaw wyników uporządkować w innej. Być może uda się to rozwiązać za pomocą podzapytań, ale uważam, że dokumenty stwierdzają również, że kolejność podzapytań może zostać zignorowana, chyba że zostanie użyte LIMIT i może to mieć wpływ występ

  • Nie zagłębiłem się w to poza testowaniem, czy metoda działa, ale jeśli istnieje ryzyko, że predykaty w drugim KIEDY zostaną zoptymalizowane (wszystko w porównaniu do wartości null jest zerowe / fałszywe, więc po co zawracać sobie głowy przypisaniem) i nie jest wykonywane , również się zatrzymuje. Wydaje mi się, że tak się nie dzieje, ale chętnie przyjmę komentarze i zaproponuję rozwiązanie, jeśli mogłoby się to zdarzyć

  • Rozsądne może być rzutowanie wartości null tworzących @pcolX na rzeczywiste typy kolumn w podzapytaniu, które tworzy zmienne @pcolX, a mianowicie: select @pcol1 := CAST(null as INT), @pcol2 := CAST(null as DATE)


Nie ma na to uzasadnienia. Podobnie jak inne odpowiedzi, które przypisują i odczytują z tej samej zmiennej.
philipxy

Czy możesz podać więcej szczegółów phil?
Caius Jard

Zobacz inne moje komentarze na tej stronie. Googling 'site: stackoverflow.com zmienna mysql „philipxy” (ustaw LUB przypisz LUB przypisz OR lub napisz) czytaj ”: Odpowiedź przeze mnie i raport o błędzie w komentarzu do mnie na to pytanie, gdzie zaakceptowana odpowiedź cytuje instrukcję natychmiast w roszczeniach jest w porządku zrobić coś sprzecznego z tym. Przeczytaj ręczne zmienne i przypisanie.
philipxy,


Rozumiem twoją troskę
Caius Jard

0

To nie jest najsolidniejsze rozwiązanie - ale jeśli chcesz po prostu utworzyć partycjonowany ranking na polu z kilkoma różnymi wartościami, może nie być nietypowo używać niektórych przypadków, gdy logika zawiera tyle zmiennych, ile potrzebujesz.

Coś takiego działało w przeszłości dla mnie:

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

Nadzieja, która ma sens / pomaga!


-1

To działa idealnie dla mnie, aby utworzyć RowNumber, gdy mamy więcej niż jedną kolumnę. W tym przypadku dwie kolumny.

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
Spróbuj sformatować dowolne odpowiedzi i podać dodatkowy kontekst tego, co próbujesz zrobić. W tej chwili jest to tylko źle sformatowany tekst.
Yannick Meeus,

2
Wydaje się, że nie ma to żadnego związku z pierwotnym pytaniem. Jeśli masz własne pytanie, zadaj je osobno.
Jeroen Mostert

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.