Un modo per vedere cosa è appena successo
Quando esegui un INSERT, un UPDATE o un DELETE, SQLite ti dice quante righe sono state toccate, ma non quali righe né quali sono i loro valori finali. La soluzione classica è una SELECT successiva. Sono due passaggi, due istruzioni e una piccola finestra in cui qualcun altro potrebbe modificare la riga nel frattempo.
RETURNING risolve il problema. Lo aggiungi a un'istruzione di scrittura, elenchi le colonne che vuoi indietro e SQLite ti consegna le righe coinvolte come se avessi appena eseguito una SELECT su di esse:
Un'istruzione, un passaggio, e ottieni l'id generato e il valore predefinito di created_at che il database ha compilato per te.
RETURNING è stato aggiunto in SQLite 3.35.0 (marzo 2021). Se la tua istruzione viene rifiutata con un errore di sintassi, verifica con SELECT sqlite_version();: le build più vecchie non conoscono la parola chiave.
Recuperare l'ID generato
Il motivo più comune per usare RETURNING è recuperare la chiave primaria generata in automatico subito dopo l'inserimento:
Prima di RETURNING, facevi l'insert e poi chiamavi last_insert_rowid() (o l'equivalente del tuo driver) sulla stessa connessione. Funziona ancora, ma si basa sullo stato della connessione: è facile sbagliare con pool di connessioni o thread. RETURNING id è esplicito, limitato all'istruzione e funziona allo stesso modo qualunque cosa ospiti la connessione.
Se la tua tabella non dichiara un INTEGER PRIMARY KEY esplicito, puoi comunque ottenere l'identificatore implicito della riga:
Ogni tabella SQLite normale ha un rowid, e RETURNING te lo consegna.
Più colonne ed espressioni
RETURNING accetta la stessa forma della lista di colonne di una SELECT. Elenca colonne, usa *, costruisci espressioni, assegna alias:
RETURNING * è comodo quando vuoi tutto, compresi i valori predefiniti compilati dal database, senza nominare ogni colonna:
Vedi il nuovo id, il name che hai passato e il timestamp calcolato da SQLite.
RETURNING con UPDATE
Con un UPDATE, RETURNING ti dà i valori dopo l'aggiornamento, cioè la riga com'è dopo che le tue modifiche sono state applicate:
Ottieni il nuovo saldo di Ada, 125, non il vecchio 100. Questo rende RETURNING perfetto per contatori atomici e operazioni di accredito e addebito: non devi leggere, calcolare, scrivere e rileggere.
Se il WHERE corrisponde a più righe, ottieni una riga per ogni riga coinvolta:
Tre righe in ingresso, tre righe in uscita. L'ordine non è garantito: se ti serve un ordine preciso, ordina il risultato lato client.
RETURNING con DELETE
Con DELETE, RETURNING ti dà le righe com'erano subito prima della cancellazione. Utile per archiviare, per le tracce di audit o semplicemente per confermare cosa è stato rimosso:
Ottieni le due sessioni scadute con tutti i loro campi intatti, anche se non esistono più nella tabella. Se vuoi spostarle altrove, è la base perfetta per una tabella di archivio: leggi il risultato e inseriscilo altrove nella stessa transazione.
RETURNING con UPSERT
RETURNING funziona anche con INSERT ... ON CONFLICT ... DO UPDATE. La riga restituita riflette il ramo che è stato eseguito, il nuovo insert o l'aggiornamento per conflitto:
Esegui l'istruzione due volte. La prima volta inserisce e restituisce ('visits', 1). La seconda volta scatta il conflitto, il valore viene incrementato e ottieni ('visits', 2). In entrambi i casi, un'istruzione e una riga in uscita: non devi chiederti "ha inserito o aggiornato?" prima di proseguire.
È lo schema più pulito in SQLite per "dammi il valore attuale, creandolo se serve" senza passaggi aggiuntivi.
Alcune cose da sapere
Qualche dettaglio che coglie di sorpresa:
RETURNINGvede sempre la riga dopo la modifica perINSERTeUPDATE, e prima della modifica perDELETE. Non c'è una sintassi per chiedere l'altro lato.- L'ordine delle righe restituite non è garantito. Aggiungi un
ORDER BYlato client se ti interessa. - Non puoi mettere
RETURNINGdentro una subquery. È una clausola di primo livello dell'istruzione di scrittura, non un'espressione. RETURNINGnon restituisce i dati modificati dai triggerBEFORE: restituisce i valori effettivamente scritti. I triggerAFTERvengono eseguiti tra la scrittura e la restituzione della riga.- Le colonne generate e i valori
DEFAULTsono visibili nel risultato. È proprio per questo cheRETURNING *è un modo rapido per controllare cosa ha compilato il database al posto tuo.
Prossimo passo: importare dati CSV
RETURNING è ottimo quando scrivi una riga o poche alla volta e vuoi vedere subito il risultato. Quando carichi migliaia di righe da un file, userai invece gli strumenti di importazione CSV di SQLite: è la prossima pagina.
Domande frequenti
SQLite supporta la clausola RETURNING?
Sì, dalla versione 3.35.0 (uscita a marzo 2021). Puoi aggiungere RETURNING alle istruzioni INSERT, UPDATE e DELETE per ottenere le righe coinvolte. Se usi una versione di SQLite più vecchia, il parser la rifiuterà: verifica con SELECT sqlite_version();.
Come ottengo l'ID di una riga appena inserita in SQLite?
Usa INSERT ... RETURNING id (oppure RETURNING rowid se la tabella non ha una chiave primaria esplicita). Ti restituisce il valore generato all'interno della stessa istruzione, quindi non ti serve una seconda query come last_insert_rowid().
RETURNING può restituire più di una colonna?
Sì. Elenca le colonne che vuoi separate da virgole, proprio come in una SELECT: RETURNING id, name, created_at. Puoi anche usare RETURNING * per ottenere tutte le colonne, oppure scrivere espressioni come RETURNING id, price * quantity AS total.
RETURNING funziona con UPSERT e ON CONFLICT?
Sì. INSERT ... ON CONFLICT ... DO UPDATE ... RETURNING ... restituisce la riga sia che sia stata appena inserita sia che sia stata aggiornata dalla gestione del conflitto. È il modo più pulito per fare un upsert e leggere lo stato risultante in un solo passaggio.