Storia della domanda:
Lo standard SQL è stato originariamente progettato come un linguaggio dichiarativo senza elementi di programmazione classica (cicli, ramificazioni, salto diretto), tuttavia con estensioni come T-SQL/PLSQL sono diventate disponibili costrutti come WHILE, CASE, persino un'imitazione di GOTO.
Problema:
Le operazioni iterative (ciclo di elaborazione di singole righe) spesso portano a rallentamenti, specialmente con grandi volumi di dati, se non sostituite con un approccio "basato su set". Le ramificazioni, CASE, IF sono molto comode, ma con un eccessivo annidamento soffre la leggibilità e la prevedibilità del codice.
Soluzione:
L'uso di strutture di controllo dovrebbe essere giustificato solo quando non è possibile fare a meno dell'elaborazione in blocco (bulk/set)! Per calcoli complessi, è consentito un piccolo ciclo, trigger o CASE. Per l'elaborazione di massa, è meglio utilizzare funzioni di finestra o UPDATE con una sottoquery.
Esempio di codice (T-SQL):
DECLARE @i INT = 1 WHILE (@i <= 5) BEGIN IF @i % 2 = 0 PRINT CONCAT('Pari: ', @i) ELSE PRINT CONCAT('Dispari: ', @i) SET @i = @i + 1 END
Espressione CASE:
SELECT num, CASE WHEN num % 2 = 0 THEN 'pari' ELSE 'dispari' END AS parità FROM numbers
Caratteristiche chiave:
Può CASE essere utilizzato per filtrare righe, come WHERE?
No! CASE restituisce valori diversi, ma non filtra righe. Un errore comune è "filtrare" tramite CASE invece di WHERE, il risultato sarà errato.
Qual è la differenza tra WHILE e CURSOR — non è la stessa cosa?
WHILE è un ciclo di base, con gestione personalizzata della variabile; CURSOR lavora sulle righe della tabella, mantenendo un riferimento alla riga. CURSOR è più impegnativo in termini di risorse e spesso funziona molto più lentamente con grandi volumi di dati.
Quale approccio è più veloce per l'elaborazione di massa: ciclo WHILE con UPDATE o un singolo UPDATE basato su set?
Nel 99% dei casi un singolo UPDATE basato su set (o INSERT) è di gran lunga più veloce di un ciclo su una singola riga (anche se sembra più "flessibile").
-- Approccio errato DECLARE @id INT = 1 WHILE (@id <= 100000) BEGIN UPDATE t SET flag=1 WHERE id=@id SET @id = @id + 1 END -- Corretto UPDATE t SET flag=1 WHERE id BETWEEN 1 AND 100000
Nel progetto per aggiornare lo stato di un milione di ordini è stato scritto un ciclo con UPDATE per ogni id. L'elaborazione è durata ~8 ore. In caso di errore a metà, perdevamo tutto e dovevamo ripararlo manualmente.
Pro:
Contro:
È stato riscritto con un UPDATE basato su set con un'unica espressione. Il tempo di esecuzione è stato ridotto a 6 minuti, l'elaborazione è atomica.
Pro:
Contro: