SQL Server, una delle più potenti piattaforme di gestione dei database relazionali, offre una vasta gamma di funzionalità per la manipolazione e l’interrogazione dei dati. Tra queste, la clausola APPLY
(nelle sue varianti CROSS APPLY
e OUTER APPLY
) rappresenta uno strumento estremamente versatile e, a volte, poco compreso, ma fondamentale per risolvere scenari complessi di join e correlazione tra tabelle. Questo articolo è progettato per demistificare CROSS APPLY
, esplorando il suo funzionamento, i suoi casi d’uso principali e come si distingue dalle operazioni di join più tradizionali.
Cos’è CROSS APPLY
?
Immagina di avere una tabella principale e di voler eseguire una funzione a valori di tabella (TVF – Table-Valued Function) o una subquery su ogni singola riga della tabella principale, e poi unire i risultati. È esattamente qui che entra in gioco CROSS APPLY
.
Tecnicamente, CROSS APPLY
agisce come un operatore di join correlato. La sua sintassi è:
SQL
SELECT
T1.ColonnaA,
T1.ColonnaB,
T2.ColonnaC
FROM
Tabella1 AS T1
CROSS APPLY
(SELECT ... FROM Tabella2 WHERE T2.ID = T1.ID) AS T2; -- Qui la subquery o TVF
Per ogni riga della Tabella1
(la tabella “esterna”), la subquery o la TVF specificata dopo CROSS APPLY
viene eseguita. Se questa subquery/TVF restituisce delle righe, queste vengono unite (come un INNER JOIN
) alla riga corrente della Tabella1
. Se la subquery/TVF non restituisce alcuna riga per una data riga della Tabella1
, quella riga della Tabella1
non apparirà nel risultato finale.
Punti chiave:
- Correlazione: la query all’interno di
APPLY
può referenziare colonne della tabella “esterna” (quella prima diAPPLY
). - Esecuzione riga per riga: la query all’interno di
APPLY
viene logicamente eseguita per ogni riga della tabella esterna. - Comportamento da
INNER JOIN
: se la subquery/TVF non produce risultati per una riga esterna, quella riga viene esclusa.
Quando Usare CROSS APPLY
? Casi d’Uso Comuni
CROSS APPLY
brilla in scenari dove un semplice JOIN
non è sufficiente o risulta inefficiente. Ecco alcuni dei casi d’uso più frequenti:
1. Funzioni a Valori di Tabella (TVF)
Questo è l’esempio più diretto. Se hai una TVF che richiede un parametro da una colonna della tua tabella principale, CROSS APPLY
è la soluzione perfetta.
Scenario: Hai una tabella Ordini
e una TVF dbo.GetDettaglioOrdine(@OrderID)
che restituisce i prodotti di un ordine.
SQL
-- Esempio teorico di una TVF (non eseguibile così com'è)
/*
CREATE FUNCTION dbo.GetDettaglioOrdine (@OrderID INT)
RETURNS TABLE
AS
RETURN
(
SELECT Prodotto, Quantita, Prezzo
FROM DettagliOrdine
WHERE OrderID = @OrderID
);
*/
-- Utilizzo di CROSS APPLY con la TVF
SELECT
o.OrderID,
o.DataOrdine,
d.Prodotto,
d.Quantita,
d.Prezzo
FROM
Ordini AS o
CROSS APPLY
dbo.GetDettaglioOrdine(o.OrderID) AS d;
Per ogni ordine, la funzione GetDettaglioOrdine
viene chiamata con l’ID dell’ordine, e i risultati vengono uniti.
2. Trovare i “Top N” per Gruppo
Questo è un classico problema dove CROSS APPLY
offre una soluzione elegante e spesso performante. Vuoi trovare, ad esempio, i 3 prodotti più venduti per ogni categoria.
Scenario: Hai una tabella Prodotti
e una tabella Vendite
. Vuoi i 2 prodotti più venduti per ogni categoria di prodotto.
SQL
-- Dati di esempio
CREATE TABLE Categorie (
CategoriaID INT PRIMARY KEY,
NomeCategoria VARCHAR(50)
);
CREATE TABLE Prodotti (
ProdottoID INT PRIMARY KEY,
NomeProdotto VARCHAR(50),
CategoriaID INT REFERENCES Categorie(CategoriaID)
);
CREATE TABLE Vendite (
VenditaID INT PRIMARY KEY,
ProdottoID INT REFERENCES Prodotti(ProdottoID),
QuantitaVenduta INT
);
INSERT INTO Categorie VALUES (1, 'Elettronica'), (2, 'Abbigliamento'), (3, 'Libri');
INSERT INTO Prodotti VALUES (101, 'Smartphone', 1), (102, 'Laptop', 1), (103, 'Cuffie', 1),
(201, 'Maglietta', 2), (202, 'Pantaloni', 2), (203, 'Cappello', 2),
(301, 'Romanzo', 3), (302, 'Saggio', 3), (303, 'Fumetto', 3);
INSERT INTO Vendite VALUES (1, 101, 50), (2, 102, 30), (3, 103, 40), (4, 101, 60),
(5, 201, 100), (6, 202, 80), (7, 203, 90),
(8, 301, 20), (9, 302, 25), (10, 303, 15);
-- La query con CROSS APPLY
SELECT
c.NomeCategoria,
p.NomeProdotto,
tv.TotaleVenduto
FROM
Categorie AS c
CROSS APPLY
(
SELECT TOP 2
p_inner.NomeProdotto,
SUM(v.QuantitaVenduta) AS TotaleVenduto
FROM
Prodotti AS p_inner
JOIN
Vendite AS v ON p_inner.ProdottoID = v.ProdottoID
WHERE
p_inner.CategoriaID = c.CategoriaID -- Correlazione qui!
GROUP BY
p_inner.NomeProdotto
ORDER BY
TotaleVenduto DESC
) AS tv
JOIN
Prodotti AS p ON p.NomeProdotto = tv.NomeProdotto AND p.CategoriaID = c.CategoriaID; -- Unione finale per ottenere i dettagli del prodotto
-- Pulisco i dati di test
DROP TABLE Vendite;
DROP TABLE Prodotti;
DROP TABLE Categorie;
In questo esempio, per ogni categoria, la subquery CROSS APPLY
seleziona i 2 prodotti più venduti di quella specifica categoria.
3. Scomposizione di Stringhe o XML/JSON
Se hai una colonna che contiene dati complessi (es. una stringa con valori separati da virgole, un XML o un JSON) e devi “esploderli” in righe separate, CROSS APPLY
è lo strumento ideale con le funzioni appropriate (es. STRING_SPLIT
, OPENJSON
, OPENXML
).
Scenario: Hai una tabella di articoli con una colonna Tags
che è una stringa delimitata da virgole. Vuoi ottenere ogni tag su una riga separata.
SQL
CREATE TABLE Articoli (
ArticoloID INT PRIMARY KEY,
Titolo VARCHAR(100),
Tags VARCHAR(200)
);
INSERT INTO Articoli VALUES
(1, 'Guida SQL Server', 'SQL,Database,Performance'),
(2, 'Concetti di .NET', 'C#,OOP,Framework'),
(3, 'Web Development', 'HTML,CSS,JavaScript');
SELECT
a.Titolo,
t.value AS Tag
FROM
Articoli AS a
CROSS APPLY
STRING_SPLIT(a.Tags, ',') AS t;
-- Pulisco i dati di test
DROP TABLE Articoli;
Qui, STRING_SPLIT
(disponibile da SQL Server 2016) viene applicato per ogni riga della tabella Articoli
, scomponendo la stringa Tags
in singole righe.
CROSS APPLY
vs. INNER JOIN
La differenza principale sta nella dipendenza della query secondaria dalla query principale.
-
INNER JOIN
: Le tabelle vengono unite in base a una condizione di join. Le operazioni di filtro o le subquery nella clausolaON
oWHERE
vengono valutate in base a valori che sono già stati determinati dall’unione delle tabelle. Le subquery in unJOIN
sono tipicamente non correlate (o correlate solo a una singola tabella all’interno dellaJOIN
stessa). -
CROSS APPLY
: La subquery o la TVF sul lato destro diAPPLY
viene eseguita per ogni riga della tabella sinistra. Può, e spesso lo fa, fare riferimento a colonne della tabella di sinistra (la tabella “esterna”). Questo permette una logica “riga per riga” che un sempliceINNER JOIN
non può fornire direttamente per le subquery.
In molti casi, una query con CROSS APPLY
può essere riscritta usando una CTE (Common Table Expression
) con ROW_NUMBER()
o subquery complesse, ma CROSS APPLY
spesso rende il codice più leggibile e, in alcuni scenari, l’ottimizzatore di query può trovare un piano di esecuzione più efficiente.
CROSS APPLY
vs. OUTER APPLY
Abbiamo parlato finora di CROSS APPLY
che si comporta come un INNER JOIN
, escludendo le righe della tabella esterna se la subquery/TVF non produce risultati.
Esiste anche OUTER APPLY
, che si comporta come un LEFT OUTER JOIN
. Se la subquery/TVF non restituisce righe per una determinata riga della tabella esterna, quella riga della tabella esterna viene comunque inclusa nel risultato, con i valori delle colonne della subquery/TVF impostati a NULL
.
Quando usare OUTER APPLY?
Quando vuoi vedere tutte le righe della tabella esterna, anche se non c’è una corrispondenza nella subquery o nella TVF.
CROSS APPLY
è uno strumento potente e flessibile in SQL Server che ogni sviluppatore dovrebbe padroneggiare. Permette di risolvere scenari complessi come il “top N per gruppo”, l’interrogazione di TVF e la scomposizione di stringhe o dati semi-strutturati, offrendo una sintassi chiara e prestazioni spesso superiori rispetto a soluzioni alternative più tortuose.
Se ti trovi a scrivere subquery correlate che si ripetono o a lottare con la logica “per ogni riga”, pensa a CROSS APPLY
. Potrebbe essere esattamente la soluzione che stai cercando.