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) = null
sono 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)
greatest-n-per-group
per guidarti a domande simili.