ROW_NUMBER () in MySQL


281

Esiste un modo carino in MySQL per replicare la funzione di SQL Server ROW_NUMBER()?

Per esempio:

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

Quindi, ad esempio, potrei aggiungere una condizione per limitare intRowa 1 per ottenere una singola riga con il massimo col3per ogni (col1, col2)coppia.


9
Taggato con greatest-n-per-groupper guidarti a domande simili.
Bill Karwin,

1
Sql-ServerTag rimosso poiché questo è stato l'elemento più votato nella ricerca di tag combinata ma non è realmente rilevante per SQL Server.
Martin Smith,

per una semplice funzione di numero di riga mysql, dai un'occhiata a datamakessense.com/mysql-rownum-row-number-function
AdrianBR

Per MySQL, le uniche soluzioni efficienti sono discusse in mysql.rjweb.org/doc.php/groupwise_max . Molte soluzioni pubblicizzate richiedono una scansione completa della tabella (o peggio).
Rick James,

2
Soluzione spudoratamente autopromozione per la vera funzione analitica ROW_NUMBER, RANK, DESNSE_RANK in MySQL
Kenneth Xu

Risposte:


102

Voglio la riga con il singolo col3 più alto per ogni coppia (col1, col2).

Questo è un massimo a livello di gruppo , una delle domande SQL più comunemente poste (dal momento che sembra che dovrebbe essere facile, ma in realtà non lo è).

Spesso cerco un 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;

"Ottieni le righe nella tabella per le quali nessun'altra riga con col1 corrispondente, col2 ha un col3 più alto." (Noterai questa e la maggior parte delle altre soluzioni massime a livello di gruppo restituiranno più righe se più di una riga ha lo stesso col1, col2, col3. Se questo è un problema, potresti aver bisogno di un po 'di post-elaborazione.)


2
E se ci fossero due valori massimi di col3 per una coppia (col1, col2)? Si finirebbe con due file.
Paul,

@Paul: si! Ho appena aggiunto una nota a riguardo nella risposta un tic fa. Di solito è possibile rilasciare facilmente righe extra indesiderate nel livello applicazione in seguito su una base casuale, ma se si hanno molte righe tutte con la stessa col3 può essere problematico.
Bobince,

1
A quanto pare, la soluzione è diventata piuttosto popolare qui su SO, ma ho una domanda. La soluzione è sostanzialmente la stessa di se qualcuno provasse a trovare l'id più grande con la seguente query: SELECT t1.id FROM test t1 LEFT JOIN test t2 ON t1.id>t2.id WHERE t2.id IS NULL;Non è necessario il n*n/2 + n/2confronto IS NULL per trovare la riga singola? Ci sono delle ottimizzazioni che non vedo? Ho provato a porre la domanda simile a Bill in un altro thread, ma sembra averlo ignorato.
dal

2
@Paul - Per affrontare il caso in cui esistono più righe che corrispondono al massimo per gruppo e si desidera afferrarne solo una, è sempre possibile aggiungere la chiave primaria nella logica della clausola ON per interrompere il legame ... SELEZIONARE la tabella 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) DOVE t1.col1 È NULL;
Jon Armstrong - Xgc

2
Questo sarebbe più leggibile comeSELECT 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

Non esiste alcuna funzionalità di classificazione in MySQL. Il più vicino che puoi ottenere è usare una variabile:

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

quindi come funzionerebbe nel mio caso? Avrei bisogno di due variabili, una per ciascuna di col1 e col2? Col2 avrebbe bisogno di essere ripristinato in qualche modo quando col1 è cambiato ..?

Sì. Se fosse Oracle, potresti usare la funzione LEAD per raggiungere il valore successivo. Per fortuna, Quassnoi copre la logica di ciò che è necessario implementare in MySQL .


1
Hmm .... quindi come funzionerebbe nel mio caso? Avrei bisogno di due variabili, una per ciascuna di col1 e col2? Col2 avrebbe bisogno di essere ripristinato in qualche modo quando col1 è cambiato ..?
Paul,

Grazie ... come ho detto sopra, questa risposta è ugualmente accettata da quella di Bobince, ma posso solo selezionarne una :-)
Paul,

9
L'assegnazione e la lettura da variabili definite dall'utente nella stessa istruzione non è affidabile. questo è documentato qui: dev.mysql.com/doc/refman/5.0/en/user-variables.html : "Come regola generale, non si dovrebbe mai assegnare un valore a una variabile utente e leggere il valore all'interno della stessa istruzione. Potresti ottenere i risultati che ti aspetti, ma ciò non è garantito. L'ordine di valutazione per le espressioni che coinvolgono le variabili utente non è definito e può cambiare in base agli elementi contenuti in una determinata istruzione. "
Roland Bouman,

1
@Roland: ho testato solo su piccoli set di dati, non ho avuto problemi. Peccato che MySQL non abbia ancora affrontato la funzionalità - la richiesta è in
arrivo

2
Questo sembra essere un comportamento indefinito come osserva Roland. ad esempio, questo dà risultati totalmente errati per un tavolo che ho provato: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

Finisco sempre per seguire questo schema. Data questa tabella:

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

Puoi ottenere questo risultato:

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

Eseguendo questa query, che non richiede alcuna variabile definita:

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

Spero che aiuti!


1
se le colonne sono VARCHAR o CHAR, come puoi gestirlo con questa struttura?
Tushar,

3
Sei fantastico Mosty, sto cercando esattamente questo
luckykrrish,

Ho appena dato questa risposta usando la tua logica per row_number. Grazie.
Utsav,

@Tushar gli operatori <, >, <=, >=maniglia CHAR e tipi di dati VARCHAR in ordine alfabetico; Mi aspetto, è esattamente quello che stai cercando.
alex,

1
@AlmazVildanov dovresti essere in grado di utilizzare questa query semplicemente come una subquery per filtrare row_numbers <= 2 ed enorme grazie per questa risposta Mosty, è perfetto!
Zax,

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

1
Il primo: = sembra mancare nella risposta di @OMG Pony. Grazie per aver pubblicato questo Peter Johnson.
Sholsinger,

Immagino (SELEZIONA @i: = 0) AS foo dovrebbe essere la prima tabella
nell'istruzione

Perché hai persino bisogno del '.. as foo'?
Tom Chiverton,

@TomChiverton Se manca, ottieni: "Codice errore: 1248. Ogni tabella derivata deve avere il proprio alias"
ExStackChanger,

1
L'assegnazione del grado qui è completamente indefinita e questo non risponde nemmeno alla domanda
jberryman,

27

Dai un'occhiata a questo articolo, mostra come imitare SQL ROW_NUMBER () con una partizione in MySQL. Mi sono imbattuto in questo stesso scenario in un'implementazione di WordPress. Avevo bisogno di ROW_NUMBER () e non c'era.

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

L'esempio nell'articolo utilizza una singola partizione per campo. Per partizionare per campi aggiuntivi potresti fare qualcosa del genere:

  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'uso di concat_ws gestisce i null. Ho provato questo su 3 campi usando int, date e varchar. Spero che questo ti aiuti. Consulta l'articolo in quanto interrompe questa query e la spiega.


1
Eccezionale. Questo effettivamente fa il partizionamento. Molto utile
Stuart Watt

1
Rispetto al self join, questo è molto più efficiente, ma c'è un problema con la logica, l'ordine deve avvenire prima di calcolare row_num, anche il concat non è necessario. `` `SELEZIONA @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

Se hai bisogno di metterlo in una sottoquery, allora aggiungi limit 18446744073709551615alla order byclausola force .
xmedeko

concat_wscon stringa vuota ''è pericoloso: concat_ws('',12,3) = concat_ws('',1,23). Meglio usare un separatore '_'o usare la soluzione @Kenneth Xu.
xmedeko

il link di op è morto; archivio del link qui
user2426679

25

Da MySQL 8.0.0e sopra puoi usare nativamente le funzioni con finestre.

1.4 Novità di MySQL 8.0 :

Funzioni della finestra.

MySQL ora supporta le funzioni della finestra che, per ogni riga di una query, eseguono un calcolo usando le righe relative a quella riga. Questi includono funzioni come RANK (), LAG () e NTILE (). Inoltre, diverse funzioni di aggregazione esistenti ora possono essere utilizzate come funzioni di finestra; ad esempio, SUM () e AVG ().

ROW_NUMBER () over_clause :

Restituisce il numero della riga corrente all'interno della sua partizione. I numeri di righe vanno da 1 al numero di righe di partizione.

ORDER BY influenza l'ordine in cui le righe sono numerate. Senza ORDER BY, la numerazione delle righe è indeterminata.

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
sospiro ... finalmente!
Used_By_Al già

15

Vorrei anche votare per la soluzione Mosty Mostacho con una piccola modifica al suo codice di query:

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

Che darà lo stesso risultato:

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

per la tabella:

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

Con la sola differenza che la query non utilizza JOIN e GROUP BY, basandosi invece sulla selezione nidificata.


Questo dovrebbe essere migliore? Entrambi sembrano probabilmente quadratici, ma non sono sicuro di come interpretare l'output EXPLAIN
jberryman,

In effetti, le selezioni nidificate non sono molto ottimizzate in MySQL, quindi questa risposta è solo per la dimostrazione di una tecnica di query. Gli esempi basati su variabili sopra funzionano meglio per la maggior parte dei casi pratici, suppongo.
abcdn,

1
Non sono convinto che nessuna delle risposte basate su variabili stia effettivamente usando un comportamento definito ...
jberryman,

Mi dispiace, non sono sicuro di aver capito cosa intendevi per "comportamento definito". Vuoi dire che non funziona per te o sei solo preoccupato che non sia documentato?
abcdn,

1
"Comportamento indefinito" significa che non è documentato per funzionare e / o documentato per non essere garantito per funzionare. Vedi citazioni e collegamenti alla documentazione nei commenti su questa pagina. Si potrebbe restituire ciò che uno (unsoundly) vuole / supposizioni / ipotizza / fantastica. Per alcune versioni dell'implementazione, alcuni programmatori di Percona hanno dimostrato che alcune espressioni di query che utilizzano CASE incrementando e utilizzando le variabili esaminano il codice. Ciò potrebbe cambiare con qualsiasi versione.
philipxy,

12

Definirei una funzione:

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

allora potrei fare:

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

Ora non hai una sottoquery, che non puoi avere nelle viste.


Funziona con una limitazione: se esegui la query più volte, otterrai falsi ID sempre crescenti per lo stesso set di risultati
Stephan Richter,

potresti inviare set @fakeId = 0; ogni volta che si desidera eseguire la query, non ottimale ma funziona
jmpeace il

Un problema davvero strano si verifica se si rimuove DETERMINISTIC. Quindi il fakeId non è corretto quando si utilizza order by. Perchè è questo?
Chris Muench,

8

query per row_number in mysql

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

Questo può essere utilizzato su query UPDATE? Sto provando ma ricevo un errore "dati troncati per colonna ...".
Diego,

1
Se qualcuno è interessato a usarlo su UPDATE, deve funzionare come sottoquery per funzionare. UPDATE <table> SET <field> = (SELECT \ @row_number: = \ @row_number +1) ORDINA PER <colonna ordine>; La colonna dell'ordine determina l'ordinamento dei valori delle righe.
Diego,

8

Non c'è divertimento come rownum, row_num()in MySQL, ma il modo per aggirare è come di seguito:

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

4

La soluzione che ho trovato per funzionare al meglio era usare una subquery come questa:

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

Le colonne PARTITION BY vengono semplicemente confrontate con '=' e separate da AND. Le colonne ORDER BY verranno confrontate con '<' o '>' e separate da OR.

Ho trovato che questo è molto flessibile, anche se è un po 'costoso.


4

La funzionalità di rownumber non può essere imitata. Potresti ottenere i risultati che ti aspetti, ma molto probabilmente rimarrai deluso ad un certo punto. Ecco cosa dice la documentazione di mysql:

Per altre dichiarazioni, come SELEZIONA, potresti ottenere i risultati che ti aspetti, ma questo non è garantito. Nella seguente dichiarazione, potresti pensare che MySQL valuterà prima @a e poi eseguirà un secondo incarico: SELECT @a, @a: = @ a + 1, ...; Tuttavia, l'ordine di valutazione per le espressioni che coinvolgono le variabili utente non è definito.

Saluti, Georgi.


Non seguo In che modo "@i: = @i + 1 come posizione" non sostituisce direttamente "ROW_NUMBER () rispetto a (ordina per somma (punteggio) desc) come posizione"?
Tom Chiverton,

1
@TomChiverton Perché il suo comportamento non è definito, come dice il manuale proprio lì.
philipxy,


2

Non vedo alcuna risposta semplice che copra la parte "PARTITION BY", quindi ecco la mia:

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 clausola ORDER BY deve riflettere la tua esigenza di ROW_NUMBER. Quindi esiste già una chiara limitazione: non è possibile avere più "emulazioni" ROW_NUMBER di questo modulo contemporaneamente.
  • L'ordine della "colonna calcolata" è importante . Se hai mysql calcolare quella colonna in un altro ordine, potrebbe non funzionare.
  • In questo semplice esempio ne ho inserito solo uno ma puoi avere più parti "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 po 'in ritardo, ma può anche aiutare a qualcuno che cerca risposte ...

Tra file / row_number esempio - query ricorsiva che può essere utilizzata in qualsiasi 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
Scusate ma per quanto ne so MySQL non supporta le espressioni di tabella comuni .
Álvaro González,

ora funziona ... @ ÁlvaroGonzález MySQL 8 supporta solo le funzioni CTE e window, quindi questa risposta non ha davvero senso utilizzare nelle versioni precedenti di MySQL ..
Raymond Nijland

1

Ciò consente di ottenere le stesse funzionalità fornite da ROW_NUMBER () E 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

Anche un po 'in ritardo, ma oggi avevo lo stesso bisogno, quindi ho cercato su Google e infine un semplice approccio generale trovato qui nell'articolo di Pinal Dave http://blog.sqlauthority.com/2014/03/09/mysql-reset-row -numero-for-each-group-partizione per riga-numero /

Volevo concentrarmi sulla domanda originale di Paul (che era anche il mio problema), quindi riassumo la mia soluzione come esempio funzionante.

Perché vogliamo partizionare su due colonne, creerei una variabile SET durante l'iterazione per identificare se è stato avviato un nuovo gruppo.

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

Il 3 significa al primo parametro di MAKE_SET che voglio entrambi i valori nel SET (3 = 1 | 2). Ovviamente se non abbiamo due o più colonne che costruiscono i gruppi, possiamo eliminare l'operazione MAKE_SET. La costruzione è esattamente la stessa. Questo funziona per me come richiesto. Mille grazie a Pinal Dave per la sua chiara dimostrazione.


1
Si noti che ORDER BYin una sottoquery potrebbe essere ignorato (consultare mariadb.com/kb/en/mariadb/… ). La soluzione suggerita è quella di aggiungere LIMIT 18446744073709551615alla sottoquery, che forza un ordinamento. Tuttavia, ciò potrebbe causare problemi di prestazioni e non è valido per tabelle enormi davvero
spaventose

1

Questa potrebbe anche essere una soluzione:

SET @row_number = 0;

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

Tuttavia non fa alcun partizionamento e non è significativamente diverso da una risposta citata più in alto
Caius Jard

1

MySQL supporta ROW_NUMBER () dalla versione 8.0+ .

Se usi MySQL 8.0 o versioni successive, controlla la funzione ROW_NUMBER (). Altrimenti, hai emulato la funzione ROW_NUMBER ().

Row_number () è una funzione di classificazione che restituisce un numero sequenziale di una riga, a partire da 1 per la prima riga.

per la versione precedente,

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

1

Importante: considerare l'aggiornamento a MySQL 8+ e utilizzare la funzione ROW_NUMBER () definita e documentata e abbandonare i vecchi hack legati a una versione antica limitata di MySQL

Ora ecco uno di quegli hack:

Le risposte che utilizzano principalmente variabili in-query / tutte sembrano ignorare il fatto che la documentazione dice (parafrasi):

Non fare affidamento sugli elementi nell'elenco SELECT che vengono valutati in ordine dall'alto verso il basso. Non assegnare variabili in un elemento SELECT e utilizzarle in un altro

Come tale, c'è il rischio che sfornino la risposta sbagliata, perché in genere fanno un

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

Se questi vengono mai valutati dal basso verso l'alto, il numero di riga smetterà di funzionare (nessuna partizione)

Quindi dobbiamo usare qualcosa con un ordine di esecuzione garantito. Inserisci CASO QUANDO:

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

Come schema ld, l'ordine di assegnazione di prevcol è importante - prevcol deve essere confrontato con il valore della riga corrente prima di assegnargli un valore dalla riga corrente (altrimenti sarebbe il valore col delle righe correnti, non il valore col della riga precedente) .

Ecco come si adatta questo:

  • Viene valutato il primo QUANDO. Se il col di questa riga è uguale al col della riga precedente, allora @r viene incrementato e restituito dal CASE. I valori di questo led di ritorno sono memorizzati in @r. È una caratteristica di MySQL che l'assegnazione restituisce il nuovo valore di ciò che viene assegnato in @r nelle righe dei risultati.

  • Per la prima riga del set di risultati, @prevcol è null (viene inizializzato su null nella sottoquery) quindi questo predicato è falso. Questo primo predicato restituisce inoltre false ogni volta che cambia col (la riga corrente è diversa dalla riga precedente). Questo fa sì che il secondo QUANDO venga valutato.

  • Il secondo predicato QUANDO è sempre falso ed esiste puramente per assegnare un nuovo valore a @prevcol. Poiché il col di questa riga è diverso dal col della riga precedente (lo sappiamo perché se fosse lo stesso, il primo QUANDO sarebbe stato usato), dobbiamo assegnare il nuovo valore per conservarlo per il test la prossima volta. Poiché l'assegnazione viene eseguita e quindi il risultato dell'assegnazione viene confrontato con null e qualsiasi elemento identificato con null è falso, questo predicato è sempre falso. Ma almeno la valutazione ha fatto il suo lavoro nel mantenere il valore di col da questa riga, quindi può essere valutato rispetto al valore col della riga successiva

  • Poiché il secondo QUANDO è falso, significa che nelle situazioni in cui la colonna da cui stiamo partizionando (col) è cambiata, è l'ELSE che fornisce un nuovo valore per @r, riavviando la numerazione da 1

Siamo arrivati ​​a una situazione in cui questo:

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

Ha la forma generale:

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

Note:

  • P in pcol significa "partizione", o in ocol significa "ordine" - nella forma generale ho lasciato cadere "prev" dal nome della variabile per ridurre il disordine visivo

  • Le parentesi intorno (@pcolX := colX) = nullsono importanti. Senza di essi assegnerai null a @pcolX e le cose smetteranno di funzionare

  • È un compromesso il fatto che il set di risultati debba essere ordinato anche dalle colonne della partizione, affinché la colonna precedente venga confrontata per funzionare. Non puoi quindi ordinare il tuo rownumber in base a una colonna ma il tuo set di risultati ordinato in un'altra Potresti essere in grado di risolverlo con le subquery ma credo che i documenti affermino anche che l'ordinamento di subquery potrebbe essere ignorato a meno che non venga utilizzato LIMIT e questo potrebbe avere un impatto prestazione

  • Non ho approfondito oltre il test del funzionamento del metodo, ma se c'è il rischio che i predicati nel secondo QUANDO saranno ottimizzati (qualsiasi cosa rispetto a null è nulla / falso, quindi perché preoccuparsi di eseguire il compito) e non eseguito , si ferma anche. Ciò non sembra accadere nella mia esperienza, ma accetterò volentieri i commenti e proporrò una soluzione se ciò potrebbe ragionevolmente verificarsi

  • Potrebbe essere saggio lanciare i null che creano @pcolX nei tipi effettivi delle colonne, nella sottoquery che crea le variabili @pcolX, vale a dire: select @pcol1 := CAST(null as INT), @pcol2 := CAST(null as DATE)


Non c'è giustificazione per questo. Proprio come le altre risposte che assegnano e leggono dalla stessa variabile.
philipxy,

Potete fornire più dettagli phil?
Caius Jard

Vedi i miei altri commenti su questa pagina. Googling 'site: stackoverflow.com variabile "philipxy" mysql (imposta O assegna O assegnazione O scrivi) leggi': Una mia risposta e una segnalazione di bug collegata in un mio commento a questa domanda in cui la risposta accettata cita il manuale ma immediatamente nelle affermazioni è OK fare qualcosa in contraddizione con esso. Leggi il manuale variabili e riassegnazione.
philipxy,


Capisco la tua preoccupazione
Caius Jard

0

Questa non è la soluzione più robusta, ma se stai solo cercando di creare un rango partizionato su un campo con solo pochi valori diversi, potrebbe non essere ingombrante usare un caso quando la logica ha tutte le variabili che desideri.

Qualcosa del genere ha funzionato per me in passato:

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

Spero che abbia senso / aiuti!


-1

Questo funziona perfettamente per me per creare RowNumber quando abbiamo più di una colonna. In questo caso due colonne.

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
Prova a formattare qualsiasi risposta e fornisci un contesto aggiuntivo su ciò che stai cercando di fare. Al momento non è altro che testo mal formattato.
Yannick Meeus,

2
Ciò non sembra avere alcuna relazione con la domanda originale. Se hai una tua domanda, per favore, chiedila separatamente.
Jeroen Mostert,

-5
SELECT 
    col1, col2, 
    count(*) as intRow
FROM Table1
GROUP BY col1,col2
ORDER BY col3 desc
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.