Database
 sql >> Database >  >> RDS >> Database

La tua guida definitiva ai join SQL:OUTER JOIN – Parte 2

L'unione esterna è al centro della scena oggi. E questa è la parte 2 della tua guida definitiva ai join SQL. Se ti sei perso la parte 1, ecco il link.

A quanto pare, esterno è l'opposto di interno. Tuttavia, se consideri il join esterno in questo modo, sarai confuso. Per finire, non devi includere la parola esterno esplicitamente nella tua sintassi. È facoltativo!

Ma prima di immergerci, discutiamo dei null relativi agli outer join.

Null e OUTER JOIN

Quando si uniscono 2 tabelle, uno dei valori di una delle due tabelle può essere nullo. Per INNER JOIN, i record con valori nulli non corrisponderanno, verranno eliminati e non verranno visualizzati nel set di risultati. Se vuoi ottenere i record che non corrispondono, la tua unica opzione è OUTER JOIN.

Tornando ai contrari, non è l'opposto di INNER JOINs? Non del tutto, come vedrai nella prossima sezione.

Tutto su SQL Server OUTER JOIN

La comprensione dei join esterni inizia con l'output. Ecco un elenco completo di ciò che puoi aspettarti:

  • Tutti i record che soddisfano la condizione o il predicato di unione. Questa è l'espressione subito dopo la parola chiave ON, proprio come l'output INNER JOIN. Ci riferiamo a questo problema come alla riga interna .
  • Valori non NULL da sinistra tabella con le controparti nulle da destra tavolo. Ci riferiamo a questo problema come righe esterne .
  • Valori non NULL da destra tabella con le controparti nulle da sinistra tavolo. Questa è un'altra forma di righe esterne.
  • Infine, potrebbe essere una combinazione di tutte le cose descritte sopra.

Con quell'elenco, possiamo dire che OUTER JOIN restituisce le righe interne ed esterne .

  • Interno – perché i risultati esatti di INNER JOIN possono essere restituito.
  • Esterno – perché anche le righe esterne possono essere restituito.

È la differenza da INNER JOIN.

INNER JOIN RESTITUISCE SOLO LE RIGHE INTERNE. LE UNITÀ ESTERNE POSSONO RESTITUIRE SIA LE RIGHE INTERNE CHE ESTERNE

Nota che ho usato "può essere" e "può anche essere". Dipende dalla tua clausola WHERE (o se includi una clausola WHERE) se restituisce entrambe le righe interne e/o esterne.

Ma da un'istruzione SELECT, come puoi determinare qual è la tabella sinistra o destra ? Bella domanda!

Come sapere qual è il tavolo sinistro o destro in un join?

Possiamo rispondere a questa domanda con esempi:

SELECT *
FROM Table1 a
LEFT OUTER JOIN Table2 b on a.column1 = b.column1

Dall'esempio sopra, Tabella1 è la tabella di sinistra e Tabella2 è il tavolo giusto. Ora, facciamo un altro esempio. Questa volta, è un semplice multi-join.

SELECT *
FROM Table1 a
LEFT OUTER JOIN Table2 b on a.column1 = b.column1
LEFT OUTER JOIN Table3 c on b.column2 = c.column1

In questo caso, per sapere cosa c'è a sinistra o a destra, ricorda che un join funziona su 2 tabelle.

Tabella 1 è ancora la tabella di sinistra e Tabella2 è il tavolo giusto. Questo si riferisce all'unione di 2 tabelle:Tabella1 e Tabella2 . Che ne dici di unirti a Table2 e Tabella 3 ? Tabella 2 diventa la tabella di sinistra e Tabella3 è il tavolo giusto.

Se aggiungiamo una quarta tabella, Tabella3 diventa la tabella di sinistra e Tabella4 è il tavolo giusto. Ma non finisce qui. Possiamo unire un altro tavolo al Table1 . Ecco un esempio:

SELECT *
FROM Table1 a
LEFT OUTER JOIN Table2 b on a.column1 = b.column1
LEFT OUTER JOIN Table3 c on b.column2 = c.column1
LEFT OUTER JOIN Table4 d on c.column1 = d.column2
LEFT OUTER JOIN Table5 e on a.column2 = e.column1

Tabella 1 è la tabella di sinistra e Tabella5 è il tavolo giusto. Puoi fare lo stesso anche con le altre tabelle.

Va bene, torniamo all'elenco degli output previsti sopra. Possiamo anche derivare i tipi di join esterni da questi.

Tipi di join esterni

Esistono 3 tipi basati sulle uscite OUTER JOIN.

UNIONE ESTERNA SINISTRA (UNIONE SINISTRA)

LEFT JOIN restituisce le righe interne + valori non NULL da sinistra tabella con le controparti nulle della tabella giusta. Quindi, è LEFT JOIN perché la tabella di sinistra è la dominante delle due tabelle all'interno del join con valori non nulli.

ESEMPIO DI UNIONE ESTERNA SINISTRA 1
-- Return all customerIDs with orders and no orders

USE AdventureWorks
GO

SELECT
 c.CustomerID
,soh.OrderDate
FROM Sales.Customer c
LEFT OUTER JOIN Sales.SalesOrderHeader soh ON c.CustomerID = soh.CustomerID 

Nell'esempio sopra, il Cliente è la tabella di sinistra e SalesOrderHeader è il tavolo giusto. Il risultato della query è 32.166 record – comprende sia le file interne che quelle esterne. Puoi vederne una parte nella Figura 1:

Supponiamo di voler restituire solo le righe esterne oi clienti senza ordini. Per farlo, aggiungi una clausola WHERE per includere solo le righe con valori null da SalesOrderHeader .

SELECT
 c.CustomerID
,soh.OrderDate
FROM Sales.Customer c
LEFT OUTER JOIN Sales.SalesOrderHeader soh ON c.CustomerID = soh.CustomerID
WHERE soh.SalesOrderID IS NULL

Il set di risultati che ho ottenuto è 701 record . A tutti loro piace OrderDate nullo dalla Figura 1.

Se ottengo solo le righe interne, il risultato sarà 31.465 record . Posso farlo modificando la clausola WHERE per includere quei SalesOrderIDs che non sono nulli. Oppure posso cambiare il join in un INNER JOIN e rimuovere la clausola WHERE.

Per vedere se viene verificato dall'output del primo esempio senza la clausola WHERE, riassumiamo i record.

Righe interne Righe esterne Righe totali
31.465 record 701 record 32.166 record

Dalle righe totali sopra con 32.166 record, puoi vedere che si verifica con i primi risultati di esempio. Questo mostra anche come funziona LEFT OUTER JOIN.

ESEMPIO DI UNIONE ESTERNA SINISTRA 2

Questa volta, l'esempio è un multi-join. Nota anche che eliminiamo la parola chiave OUTER.

-- show the people with and without addresses from AdventureWorks
USE AdventureWorks
GO

SELECT
 P.FirstName
,P.MiddleName
,P.LastName
,a.AddressLine1
,a.AddressLine2
,a.City
,adt.Name AS AddressType
FROM Person.Person p
LEFT JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID = bea.BusinessEntityID
LEFT JOIN Person.Address a ON bea.AddressID = a.AddressID
LEFT JOIN person.AddressType adt ON bea.AddressTypeID = adt.AddressTypeID 

Ha generato 19.996 record. È possibile controllare la parte dell'output nella Figura 2 di seguito. I record con AddressLine1 null sono righe esterne. Sopra ci sono le righe interne.

UNIONE ESTERNA DESTRA (UNIONE DESTRA)

RIGHT JOIN restituisce le righe interne + valori non NULL da destra tabella con le controparti nulle della tabella di sinistra.

ESEMPIO DI UNIONE ESTERNO DESTRO 1
-- From the product reviews, return the products without product reviews
USE AdventureWorks
GO

SELECT
P.Name
FROM Production.ProductReview pr
RIGHT OUTER JOIN Production.Product p ON pr.ProductID = p.ProductID
WHERE pr.ProductReviewID IS NULL 

La figura 3 mostra 10 di 501 record nel set di risultati.

Nell'esempio sopra, ProductReview è la tabella a sinistra e il Prodotto è il tavolo giusto. Poiché si tratta di un RIGHT OUTER JOIN, intendiamo includere i valori Non NULL dalla tabella di destra.

Tuttavia, la scelta tra LEFT JOIN o RIGHT JOIN dipende da te. Come mai? Perché puoi esprimere la query, sia LEFT che RIGHT JOIN, e ottenere gli stessi risultati. Proviamolo con un JOIN SINISTRO.

-- return the products without product reviews using LEFT OUTER JOIN
USE AdventureWorks
GO

SELECT
P.Name
FROM Production.Product p
LEFT OUTER JOIN Production.ProductReview pr ON pr.ProductID = p.ProductID
WHERE pr.ProductReviewID IS NULL

Prova a eseguire quanto sopra e otterrai lo stesso risultato della Figura 3. Ma pensi che Query Optimizer li tratterà in modo diverso? Scopriamolo nel Piano di Esecuzione di entrambi in Figura 4.

Se non conosci questo, ci sono alcune sorprese nel Piano di esecuzione.

  1. I diagrammi hanno lo stesso aspetto e sono:prova un Confronta Showplan e vedrai lo stesso QueryPlanHash .
  2. Notare il diagramma in alto con un join Merge. Abbiamo usato un RIGHT OUTER JOIN, ma SQL Server lo ha cambiato in LEFT OUTER JOIN. Ha anche scambiato i tavoli sinistro e destro. Lo rende uguale alla seconda query con LEFT JOIN.

Come vedi ora, i risultati sono gli stessi. Quindi, scegli quale delle OUTER JOIN sarà più conveniente.

Perché SQL Server ha cambiato RIGHT JOIN in LEFT JOIN?

Il motore di database non deve seguire il modo in cui esprimi i join logici. Finché può produrre risultati corretti nel modo più veloce che ritiene possibile, apporterà modifiche. Anche le scorciatoie.

Non concludere che RGHT JOIN sia cattivo e LEFT JOIN sia buono.

ESEMPIO DI UNIONE ESTERNO DESTRO 2

Dai un'occhiata all'esempio seguente:

-- Get the unassigned addresses and the address types with no addresses
SELECT
 P.FirstName
,P.MiddleName
,P.LastName
,a.AddressLine1
,a.AddressLine2
,a.City
,adt.Name AS AddressType
FROM Person.Person p
RIGHT JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID = bea.BusinessEntityID
RIGHT JOIN Person.Address a ON bea.AddressID = a.AddressID
RIGHT JOIN person.AddressType adt ON bea.AddressTypeID = adt.AddressTypeID
WHERE P.BusinessEntityID IS NULL 

Ci sono 2 cose che puoi ottenere da questa query, come puoi vedere nella Figura 5 di seguito:

I risultati della query mostrano quanto segue:

  1. Gli indirizzi non assegnati:questi record sono quelli con nomi nulli.
  2. Tipi di indirizzi senza indirizzi. I tipi di indirizzo Archivio, Fatturazione e Indirizzo principale non hanno indirizzi corrispondenti. Quelli sono dai record da 817 a 819.

FULL OUTER JOIN (FULL JOIN)

FULL JOIN restituisce una combinazione di righe interne e righe esterne, sinistra e destra.

-- Get people with and without addresses, unassigned addresses, and address types without addresses
SELECT
 P.FirstName
,P.MiddleName
,P.LastName
,a.AddressLine1
,a.AddressLine2
,a.City
,adt.Name AS AddressType
FROM Person.Person p
FULL JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID = bea.BusinessEntityID
FULL JOIN Person.Address a ON bea.AddressID = a.AddressID
FULL JOIN person.AddressType adt ON bea.AddressTypeID = adt.AddressTypeID

Il set di risultati include 20.815 record. Come quello che ti aspetteresti, è un numero totale di record dal set di risultati di INNER JOIN, LEFT JOIN e RIGHT JOIN.

LEFT e RIGHT JOIN includono una clausola WHERE per mostrare solo i risultati con valori null nelle tabelle sinistra o destra.

UNIONE INTERNA UNISCI A SINISTRA
(DOVE a.AddressID È NULL)
ACCEDI A DESTRA
(DOVE P.BusinessEntityID È NULL)
TOTALE (uguale a FULL JOIN)
18.798 record 1.198 record 819 record 20.815 record

Si noti che il FULL JOIN può produrre un enorme set di risultati da tabelle di grandi dimensioni. Quindi, usalo solo quando ti serve.

Usi pratici di OUTER JOIN

Se esiti ancora quando puoi e dovresti usare OUTER JOIN, ecco alcune idee.

Uniscenze esterne che generano righe interne ed esterne

Esempi possono essere:

  • Elenco alfabetico degli ordini dei clienti pagati e non pagati.
  • Elenco alfabetico dei dipendenti con o senza record di ritardo.
  • Un elenco di assicurati che hanno rinnovato e non rinnovato le loro polizze assicurative più recenti.

Outer Join che generano solo righe esterne

Gli esempi includono:

  • elenco alfabetico dei dipendenti senza record di ritardo per il premio ritardo zero
  • elenco di territori senza clienti
  • elenco di agenti di vendita senza vendite di un particolare prodotto
  • ottenere risultati da valori mancanti, come date senza ordini cliente in un determinato periodo (esempio sotto)
  • nodi senza figli in una relazione genitore-figlio (esempio sotto)

Ottenere risultati dai valori mancanti

Supponiamo di dover produrre un rapporto. Tale rapporto deve mostrare il numero di giorni per ogni mese in un determinato periodo in cui non c'erano ordini. La Intestazione SalesOrder in AdventureWorks contiene le Data dell'ordine , ma non hanno date senza ordini. Cosa puoi fare?

1. Crea una tabella di tutte le date in un periodo

Uno script di esempio di seguito creerà una tabella di date per l'intero 2014:

DECLARE @StartDate date = '20140101', @EndDate date = '20141231';

CREATE TABLE dbo.Dates
(
	d DATE NOT null PRIMARY KEY
)

WHILE @StartDate <= @EndDate
BEGIN
  INSERT Dates([d]) SELECT @StartDate;
  SET @StartDate = DATEADD(DAY, 1, @StartDate);
END

SELECT d FROM Dates ORDER BY [d];
2. Usa LEFT JOIN per produrre i giorni senza ordini
SELECT
 MONTH(d.d) AS [month]
,YEAR(d.d) AS [year]
,COUNT(*) AS NoOrderDays
FROM Dates d
LEFT JOIN Sales.SalesOrderHeader soh ON d.d = soh.OrderDate
WHERE soh.OrderDate IS NULL
GROUP BY YEAR(d.d), MONTH(d.d)
ORDER BY [year], [month]

Il codice sopra conta il numero di giorni in cui non sono stati effettuati ordini. Intestazione ordine di vendita contiene le date con gli ordini. Pertanto, i valori null restituiti nel join conteranno come giorni senza ordini.

Nel frattempo, se vuoi conoscere le date esatte, puoi rimuovere il conteggio e il raggruppamento.

SELECT
 d.d
,soh.OrderDate
FROM Dates d
LEFT JOIN Sales.SalesOrderHeader soh ON d.d = soh.OrderDate
WHERE soh.OrderDate IS NULL

Oppure, se vuoi contare gli ordini in un determinato periodo e vedere quale data ha zero ordini, ecco come fare:

SELECT DISTINCT
 D.d AS SalesDate
,COUNT(soh.OrderDate) AS NoOfOrders
FROM Dates d
LEFT JOIN Sales.SalesOrderHeader soh ON d.d = soh.OrderDate
WHERE d.d BETWEEN '02/01/2014' AND '02/28/2014'
GROUP BY d.d
ORDER BY d.d

Il codice precedente conta gli ordini per febbraio 2014. Guarda il risultato:

Perché evidenzia il 3 febbraio 2014? Nella mia copia di AdventureWorks non ci sono ordini cliente per quella data.

Ora, nota COUNT(soh.OrderDate) nel codice. Più avanti, chiariremo perché questo è così importante.

Ottenere nodi senza figli nelle relazioni genitore-figlio

A volte abbiamo bisogno di conoscere i nodi senza figli in una relazione genitore-figlio.

Usiamo il database che ho usato nel mio articolo su HierarchyID. Devi ottenere nodi senza figli in una tabella di relazione genitore-figlio usando un self-join.

SELECT 
 r1.RankParentId
,r1.Rank AS RankParent
,r.RankId
FROM Ranks r
RIGHT JOIN Ranks r1 ON r.RankParentId = r1.RankId
WHERE r.RankId is NULL 

Avvertenze sull'utilizzo di OUTER JOIN

Poiché un OUTER JOIN può restituire righe interne come un INNER JOIN, può confondere. Anche i problemi di prestazioni possono insinuarsi. Quindi, prendi nota dei 3 punti seguenti (ci ripenso di tanto in tanto:non sto invecchiando, quindi dimentico anch'io).

Filtraggio della tabella di destra in un LEFT JOIN con un valore non nullo nella clausola WHERE

Può essere un problema se hai usato un LEFT OUTER JOIN ma hai filtrato la tabella giusta con un valore non nullo nella clausola WHERE. Il motivo è che diventerà funzionalmente equivalente a un INNER JOIN. Considera l'esempio seguente:

USE AdventureWorks
GO

SELECT
 P.FirstName
,P.MiddleName
,P.LastName
,a.AddressLine1
,a.AddressLine2
,a.City
,adt.Name AS AddressType
FROM Person.Person p
LEFT JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID = bea.BusinessEntityID
LEFT JOIN Person.Address a ON bea.AddressID = a.AddressID
LEFT JOIN person.AddressType adt ON bea.AddressTypeID = adt.AddressTypeID
WHERE bea.AddressTypeID = 5 

Dal codice sopra, esaminiamo le 2 tabelle:Persona e BusinessEntityAddress . La persona è la tabella di sinistra e BusinessEntityAddress è il tavolo giusto.

Viene utilizzato LEFT JOIN, quindi presuppone un BusinessEntityID nullo da qualche parte in BusinessEntityAddress . Qui, notare la clausola WHERE. Filtra la tabella giusta con AddressTypeID =5. Elimina completamente tutte le righe esterne in BusinessEntityAddress .

Può essere uno qualsiasi di questi:

  • Lo sviluppatore sta testando qualcosa nel risultato ma ha dimenticato di rimuoverlo.
  • Inner JOIN era previsto, ma per qualche motivo è stato utilizzato LEFT JOIN.
  • Lo sviluppatore non comprende la differenza tra LEFT JOIN e INNER JOIN. Presume che uno qualsiasi dei 2 funzioni, e non importa perché in questo caso i risultati sono gli stessi.

Uno qualsiasi dei 3 precedenti è negativo, ma la terza voce ha un'altra implicazione. Confrontiamo il codice sopra con l'equivalente INNER JOIN:

SELECT
 P.FirstName
,P.MiddleName
,P.LastName
,a.AddressLine1
,a.AddressLine2
,a.City
,adt.Name AS AddressType
FROM Person.Person p
INNER JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID = bea.BusinessEntityID
INNER JOIN Person.Address a ON bea.AddressID = a.AddressID
INNER JOIN person.AddressType adt ON bea.AddressTypeID = adt.AddressTypeID
WHERE bea.AddressTypeID = 5

È simile al codice precedente ad eccezione del tipo di join. Anche il risultato è lo stesso, ma dovresti notare le letture logiche in STATISTICS IO:

Nella Figura 7, le prime statistiche di I/O derivano dall'uso di INNER JOIN. Un totale di letture logiche è 177. Tuttavia, le seconde statistiche riguardano il LEFT JOIN con un valore di letture logiche più alto di 223. Pertanto, l'utilizzo errato di LEFT JOIN in questo esempio richiederà più pagine o risorse da SQL Server. Pertanto, funzionerà più lentamente.

Asporto

Se intendi generare righe interne, utilizza INNER JOIN. In caso contrario, non filtrare la tabella corretta in un LEFT JOIN con un valore non nullo. In questo caso, ti ritroverai con una query più lenta rispetto a quando utilizzi INNER JOIN.

SUGGERIMENTO BONUS :Questa situazione si verifica anche in un RIGHT JOIN quando la tabella di sinistra viene filtrata con un valore non nullo.

Uso improprio dei tipi di join in un multi-join

Supponiamo di voler ottenere tutto i fornitori e il numero di ordini di acquisto dei prodotti per ciascuno. Ecco il codice:

USE AdventureWorks
GO

SELECT
 v.BusinessEntityID
,v.Name AS Vendor
,pod.ProductID
,pod.OrderQty
FROM Purchasing.Vendor v
LEFT JOIN Purchasing.PurchaseOrderHeader poh ON v.BusinessEntityID = poh.VendorID
LEFT JOIN Purchasing.PurchaseOrderDetail pod ON poh.PurchaseOrderID = pod.PurchaseOrderID 

Il codice sopra riportato restituisce sia i fornitori con ordini di acquisto che quelli senza. La figura 8 mostra il piano di esecuzione effettivo del codice precedente.

Pensando che ogni ordine di acquisto abbia un dettaglio dell'ordine di acquisto garantito, un INNER JOIN sarebbe meglio. Tuttavia, è davvero così?

Per prima cosa, abbiamo il codice modificato con INNER JOIN.

USE AdventureWorks
GO

SELECT
 v.BusinessEntityID
,v.Name AS Vendor
,pod.ProductID
,pod.OrderQty
FROM Purchasing.Vendor v
LEFT JOIN Purchasing.PurchaseOrderHeader poh ON v.BusinessEntityID = poh.VendorID
INNER JOIN Purchasing.PurchaseOrderDetail pod ON poh.PurchaseOrderID = pod.PurchaseOrderID 

Ricorda, il requisito sopra dice "tutti" i fornitori. Poiché abbiamo utilizzato LEFT JOIN nel codice precedente, otterremo fornitori senza ordini di acquisto restituiti. Ciò è dovuto al PurchaseOrderID nullo .

La modifica del join in un INNER JOIN eliminerà tutti i PurchaseOrderIDs nulli. Cancellerà anche tutti i VendorID nulli dal Fornitore tavolo. In effetti, diventa un INNER JOIN.

È un presupposto corretto? Il Piano di Esecuzione rivelerà la risposta:

Come puoi vedere, tutte le tabelle sono state elaborate utilizzando INNER JOIN. Pertanto, la nostra ipotesi è corretta. Ma per la parte peggiore, il set di risultati ora non è corretto perché i fornitori senza ordini non sono stati inclusi.

Asporto

Come nel caso precedente, se intendi un INNER JOIN, utilizzalo. Ma sai cosa fare se incontri una situazione come quella qui.

In questo caso, un INNER JOIN eliminerà tutte le righe esterne fino alla prima tabella nella relazione. Anche se l'altro tuo join è un LEFT JOIN, non importa. Lo abbiamo dimostrato nei piani di esecuzione.

Uso errato di COUNT() nei join esterni

Ricordi il nostro codice di esempio che conta il numero di ordini per data e il risultato nella Figura 6?

Qui, chiariremo perché 02/03/2014 è evidenziato e la sua relazione con COUNT(soh.OrderDate) .

Se provi a utilizzare COUNT(*), il numero di ordini per quella data diventa 1, il che è sbagliato. Non ci sono ordini in quella data. Quindi, quando usi COUNT() con un OUTER JOIN, usa la colonna corretta per contare.

Nel nostro caso, soh.OrderDate può essere nullo o meno. Quando non è nullo, COUNT() includerà la riga nel conteggio. COUNT(*) farà contare tutto, inclusi i null. E alla fine, risultati sbagliati.

Gli asporto di OUTER JOIN

Riassumiamo i punti:

  • OUTER JOIN può restituire sia righe interne che righe esterne. Le righe interne sono il risultato simile al risultato di INNER JOIN. Le righe esterne sono i valori non nulli con le loro controparti nulle in base alla condizione di unione.
  • OUTER JOIN può essere SINISTRA, DESTRA o COMPLETA. Avevamo esempi per ciascuno.
  • Le righe esterne restituite da OUTER JOIN possono essere utilizzate in vari modi pratici. Avevamo idee su quando puoi usare questa roba.
  • Avevamo anche delle avvertenze nell'utilizzo di OUTER JOIN. Presta attenzione ai 3 punti precedenti per evitare bug e problemi di prestazioni.

La parte finale di questa serie discuterà di CROSS JOIN. Quindi, fino ad allora. E se ti piace questo post, condividi un po' di amore facendo clic sui pulsanti dei social media. Buona codifica!