sql >> Databasteknik >  >> RDS >> Database

Din ultimata guide till SQL Joins:OUTER JOIN – Del 2

Ytterskarv står i centrum idag. Och det här är del 2 av din ultimata guide till SQL-anslutningar. Om du missade del 1, här är länken.

När det ser ut är det yttre motsatsen till det inre. Men om du betraktar den yttre sammanfogningen på detta sätt kommer du att bli förvirrad. Till råga på det behöver du inte inkludera ordet yttre i din syntax uttryckligen. Det är valfritt!

Men innan vi dyker in, låt oss diskutera nollor angående yttre sammanfogningar.

Null och YTTRE JOIN

När du sammanfogar 2 tabeller kan ett av värdena från endera tabellen vara null. För INNER JOINs matchar inte poster med nollvärden, och de kommer att kasseras och visas inte i resultatuppsättningen. Om du vill få de poster som inte stämmer överens, är ditt enda alternativ OUTER JOIN.

Om vi ​​går tillbaka till antonymer, är det inte motsatsen till INNER JOINs? Inte helt, som du kommer att se i nästa avsnitt.

Allt om SQL Server OUTER JOIN

Att förstå yttre kopplingar börjar med utgången. Här är en komplett lista över vad du kan förvänta dig:

  • Alla poster som matchar kopplingsvillkoret eller predikatet. Det är uttrycket direkt efter nyckelordet ON, ungefär som INNER JOIN-utgången. Vi hänvisar till det här problemet som den inre raden .
  • Icke-NULL-värden från vänster tabell med nollmotsvarigheterna från höger tabell. Vi hänvisar till det här problemet som yttre rader .
  • Icke-NULL-värden från höger tabell med nollmotsvarigheterna från vänster tabell. Detta är en annan form av yttre rader.
  • Slutligen kan det vara en kombination av alla saker som beskrivs ovan.

Med den listan kan vi säga att OUTER JOIN returnerar inre och yttre rader .

  • Inner – eftersom de exakta resultaten av INNER JOIN kan vara returnerade.
  • Ytter – eftersom de yttre raderna också kan vara returnerade.

Det är skillnaden från INNER JOIN.

INRE JOINS ENDAST ÅTERKOMMANDE INNERRA RADER. YTTRE JOINS KAN RETURNERA BÅDE INRE OCH YTTRE RADER

Lägg märke till att jag använde "kan vara" och "kan också vara." Det beror på din WHERE-sats (eller om du någonsin inkluderar en WHERE-sats) om den returnerar både inre och/eller yttre rader.

Men från en SELECT-sats, hur kan du avgöra vilken som är den vänstra eller högra tabellen ? Bra fråga!

Hur vet man vilken som är vänster eller höger tabell i en Join?

Vi kan besvara denna fråga med exempel:

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

Från exemplet ovan, Tabell1 är den vänstra tabellen och Tabell2 är rätt bord. Låt oss nu ta ett annat exempel. Den här gången är det en enkel 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

I det här fallet, för att veta vad som är vänster eller höger, kom ihåg att en join fungerar på 2 bord.

Tabell1 är fortfarande den vänstra tabellen och Tabell2 är rätt bord. Detta syftar på att sammanfoga 2 tabeller:Tabell1 och Tabell2 . Vad sägs om att gå med i Tabell2 och Tabell3 ? Tabell 2 blir den vänstra tabellen och Tabell3 är rätt tabell.

Om vi ​​lägger till en fjärde tabell, Tabell3 blir den vänstra tabellen och Tabell4 är rätt bord. Men det slutar inte där. Vi kan ansluta ett annat bord till Tabell1 . Här är ett exempel:

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

Tabell1 är den vänstra tabellen och Tabell5 är rätt bord. Du kan också göra samma sak med de andra tabellerna.

Okej, låt oss gå tillbaka till listan över förväntade utgångar ovan. Vi kan också härleda de yttre sammanfogningstyperna från dessa.

Typer av yttre skarvar

Det finns 3 typer baserade på OUTER JOIN-utgångarna.

LEFT OUTER JOIN (LEFT JOIN)

LEFT JOIN returnerar inre rader + Icke-NULL-värden från vänster bord med det högra bordets nollmotsvarigheter. Därför är det LEFT JOIN eftersom den vänstra tabellen är den dominerande av de två tabellerna inom joinen som har icke-nullvärden.

VÄNSTER YTTRE JOIN EXEMPEL 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 

I exemplet ovan, Kunden är den vänstra tabellen och SalesOrderHeader är rätt bord. Resultatet av frågan är 32 166 poster – den inkluderar både inre och yttre rader. Du kan se en del av det i figur 1:

Anta att vi bara vill returnera de yttre raderna eller kunderna utan beställningar. För att göra det, lägg till en WHERE-sats för att endast inkludera rader med nollvärden från 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

Resultatuppsättningen jag fick är 701 rekord . Alla gillar OrderDate null från figur 1.

Om jag bara får de inre raderna blir resultatet 31 465 poster . Jag kan göra det genom att ändra WHERE-satsen så att den inkluderar dessa SalesOrderIDs som inte är null. Eller så kan jag ändra joinen till en INNER JOIN och ta bort WHERE-satsen.

För att se om det checkar ut från utdata från det första exemplet utan WHERE-satsen, låt oss summera posterna.

Inre rader Yttre rader Totalt antal rader
31 465 poster 701 poster 32 166 poster

Från Totalt rader ovan med 32 166 poster kan du se att det checkar ut med de första exempelresultaten. Detta visar också hur LEFT OUTER JOIN fungerar.

VÄNSTER YTTRE JOIN EXEMPEL 2

Den här gången är exemplet en multi-join. Lägg också märke till att vi tar bort nyckelordet 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 

Det genererade 19 996 rekord. Du kan kolla in delen av utdata i figur 2 nedan. Posterna med null AddressLine1 är yttre rader. Ovanför den finns inre rader.

RIGHT OUTER JOIN (RIGHT JOIN)

RIGHT JOIN returnerar inre rader + Icke-NULL-värden från höger tabell med det vänstra bordets nollmotsvarigheter.

HÖGER YTTRE JOIN EXEMPEL 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 

Figur 3 visar 10 av 501 poster i resultatuppsättningen.

I exemplet ovan, ProductReview är den vänstra tabellen och Produkten är rätt bord. Eftersom detta är en RIGHT OUTER JOIN, avser vi att inkludera Non-NULL-värdena från den högra tabellen.

Men att välja mellan LEFT JOIN eller RIGHT JOIN beror på dig. Varför? Eftersom du kan uttrycka frågan, oavsett om det är LEFT eller RIGHT JOIN, och få samma resultat. Låt oss prova det med en LEFT JOIN.

-- 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

Försök att utföra ovanstående så får du samma resultat som i figur 3. Men tror du att frågeoptimeraren kommer att behandla dem annorlunda? Låt oss ta reda på det i utförandeplanen för båda i figur 4.

Om du är ny på detta finns det några överraskningar i genomförandeplanen.

  1. Diagrammen ser likadana ut, och de är:prova en Jämför Showplan , och du kommer att se samma QueryPlanHash .
  2. Lägg märke till det översta diagrammet med en sammanfogning. Vi använde en RIGHT OUTER JOIN, men SQL Server ändrade den till LEFT OUTER JOIN. Det bytte också vänster och höger bord. Det gör det lika med den andra frågan med LEFT JOIN.

Som du ser nu är resultaten desamma. Så välj vilken av OUTTER JOIN-erna som är bekvämare.

Varför ändrade SQL Server RIGHT JOIN till LEFT JOIN?

Databasmotorn behöver inte följa hur du uttrycker de logiska kopplingarna. Så länge den kan ge korrekta resultat på det snabbaste sätt den tror möjligt, kommer den att göra förändringar. Även genvägar.

Dra inte slutsatsen att RGHT JOIN är dåligt och LEFT JOIN är bra.

HÖGER YTTRE JOIN EXEMPEL 2

Ta en titt på exemplet nedan:

-- 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 

Det finns två saker som du kan få från den här frågan, som du kan se i figur 5 nedan:

Frågeresultaten visar följande:

  1. De otilldelade adresserna – dessa poster är de med nollnamn.
  2. Adresstyper utan adresser. Arkiv-, fakturerings- och primäradresstyper har inga motsvarande adresser. De är från posterna 817 till 819.

FULLSTÄNDIG YTTRE JOIN (FULL JOIN)

FULL JOIN returnerar en kombination av inre rader och yttre rader, vänster och höger.

-- 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

Resultatuppsättningen innehåller 20 815 rekord. Precis som du förväntar dig är det ett totalt antal poster från resultatuppsättningen INNER JOIN, LEFT JOIN och RIGHT JOIN.

LEFT och RIGHT JOIN innehåller en WHERE-sats för att endast visa resultaten med nollvärden i antingen vänster eller höger tabell.

INRE JOIN VÄNSTER GÅ MED
(DÄR a.AddressID ÄR NULL)
HÖGER GÅ MED
(DÄR P.BusinessEntityID ÄR NULL)
TOTALT (Samma som FULL JOIN)
18 798 poster 1 198 poster 819 poster 20 815 poster

Observera att FULL JOIN kan ge ett enormt resultat från stora tabeller. Så använd den bara när du bara behöver den.

Praktisk användning av OUTTER JOIN

Om du fortfarande tvekar när du kan och bör använda OUTER JOIN, här är några idéer.

Ytter sammanfogar som utmatar både inre och yttre rader

Exempel kan vara:

  • Alfabetisk lista över betalda och obetalda kundorder.
  • Alfabetisk lista över anställda med försening eller ingen försening.
  • En lista över försäkringstagare som förnyat och inte förnyat sina senaste försäkringar.

Yttre sammanfogningar som endast matar ut yttre rader

Exempel inkluderar:

  • alfabetisk lista över anställda utan dröjsmålsrekord för nollsenlighetspriset
  • lista över områden utan kunder
  • lista över försäljningsagenter utan försäljning av en viss produkt
  • att få resultat från saknade värden, som datum utan försäljningsorder under en given period (exempel nedan)
  • noder utan barn i en förälder-barn-relation (exempel nedan)

Få resultat från saknade värden

Anta att du behöver göra en rapport. Den rapporten måste visa antalet dagar för varje månad under en given period där det inte fanns några beställningar. SalesOrderHeader i AdventureWorks innehåller OrderDates , men de har inga datum utan beställningar. Vad kan du göra?

1. Skapa en tabell över alla datum under en period

Ett exempelskript nedan kommer att skapa en tabell över datum för hela 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. Använd LEFT JOIN för att skriva ut dagar utan beställningar
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]

Koden ovan räknar antalet dagar då inga beställningar har gjorts. SalesOrderHeader innehåller datum med beställningar. Så null som returneras i anslutningen kommer att räknas som dagar utan beställningar.

Under tiden, om du vill veta exakta datum, kan du ta bort räkningen och grupperingen.

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

Eller om du vill räkna beställningar under en viss period och se vilket datum som har nollbeställningar, så här:

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

Ovanstående kod räknar beställningar för februari 2014. Se resultatet:

Varför framhäver den 3 februari 2014? I mitt exemplar av AdventureWorks finns det inga försäljningsorder för det datumet.

Lägg nu märke till COUNT(soh.OrderDate) i koden. Senare kommer vi att förtydliga varför detta är så viktigt.

Få barnlösa noder i relationer mellan föräldrar och barn

Ibland behöver vi känna till noderna utan barn i en förälder-barn-relation.

Låt oss använda databasen som jag har använt i min artikel om HierarchyID. Du måste få noder utan underordnade i en förälder-barn-relationstabell med hjälp av en självanslutning.

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 

Varningar i att använda OUTER JOIN

Eftersom en OUTTER JOIN kan returnera inre rader som en INNER JOIN, kan den förvirra. Prestandaproblem kan också smyga sig på. Så notera de tre punkterna nedan (jag återkommer till dem då och då – jag blir inte yngre, så jag glömmer det också).

Filtrera den högra tabellen i en LEFT JOIN med ett icke-nullvärde i WHERE-satsen

Det kan vara ett problem om du använde en LEFT OUTER JOIN men filtrerade den högra tabellen med ett icke-nullvärde i WHERE-satsen. Anledningen är att den kommer att bli funktionellt likvärdig med en INNER JOIN. Tänk på exemplet nedan:

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 

Från koden ovan, låt oss undersöka de två tabellerna:Person och BusinessEntityAddress . Personen är den vänstra tabellen och BusinessEntityAddress är rätt tabell.

LEFT JOIN används, så det antar ett BusinessEntityID null någonstans i BusinessEntityAddress . Lägg märke till WHERE-satsen här. Den filtrerar rätt tabell med AddressTypeID =5. Den kasserar helt alla yttre rader i BusinessEntityAddress .

Detta kan antingen vara någon av dessa:

  • Utvecklaren testar något i resultatet men glömde att ta bort det.
  • INNER JOIN var tänkt, men av någon anledning användes LEFT JOIN.
  • Utvecklaren förstår inte skillnaden mellan LEFT JOIN och INNER JOIN. Han antar att någon av de två kommer att fungera, och det spelar ingen roll eftersom resultaten är desamma i det här fallet.

Någon av de 3 ovan är dålig, men den tredje posten har en annan innebörd. Låt oss jämföra koden ovan med motsvarande 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

Den ser ut som den tidigare koden förutom typen av join. Resultatet är också detsamma, men du bör notera de logiska läsningarna i STATISTICS IO:

I figur 7 är den första I/O-statistiken från användningen av INNER JOIN. Totalt antal logiska läsningar är 177. Den andra statistiken är dock för LEFT JOIN med ett högre logiskt läsvärde på 223. Således kommer fel användning av LEFT JOIN i detta exempel att kräva fler sidor eller resurser från SQL Server. Därför kommer den att gå långsammare.

Hämtmat

Om du tänker skriva ut inre rader, använd INNER JOIN. Annars, filtrera inte den högra tabellen i en LEFT JOIN med ett icke-nullvärde. Om detta händer får du en långsammare fråga än om du använder INNER JOIN.

BONUSTIPS :Denna situation inträffar också i en RIGHT JOIN när den vänstra tabellen filtreras med ett icke-nullvärde.

Felaktig användning av Join-typer i en Multi-Join

Anta att vi vill få alla leverantörerna och antalet produktinköpsorder för varje. Här är koden:

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 

Ovanstående kod returnerar både leverantörer med inköpsorder och de utan. Figur 8 visar den faktiska exekveringsplanen för ovanstående kod.

Med tanke på att varje inköpsorder har en garanterad inköpsorderdetalj, skulle en INNER JOIN vara bättre. Men är det verkligen så?

Låt oss först ha den modifierade koden med 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 

Kom ihåg att kravet ovan säger "alla" leverantörer. Eftersom vi använde LEFT JOIN i den tidigare koden kommer vi att få leverantörer utan inköpsorder returnerade. Det beror på noll PurchaseOrderID .

Om du ändrar anslutningen till en INNER JOIN förkastas alla PurchaseOrderIDs med null. Det kommer också att avbryta alla null leverantörs-ID:n från Leverantören tabell. I själva verket blir det en INNER JOIN.

Är det ett korrekt antagande? Utförandeplanen kommer att avslöja svaret:

Som du ser bearbetades alla tabeller med INNER JOIN. Därför är vårt antagande korrekt. Men för det värsta är resultatuppsättningen nu felaktig eftersom leverantörerna utan beställningar inte inkluderades.

Hämtmat

Liksom i föregående fall, om du avser en INNER JOIN, använd den. Men du vet vad du ska göra om du råkar ut för en situation som den här.

I det här fallet kommer en INNER JOIN att förkasta alla yttre rader upp till den översta tabellen i relationen. Även om ditt andra medlem är en LEFT JOIN, spelar det ingen roll. Det har vi bevisat i genomförandeplanerna.

Felaktig användning av COUNT() i Outer Joins

Kommer du ihåg vår exempelkod som räknar antalet beställningar per datum och resultatet i figur 6?

Här kommer vi att klargöra varför 02/03/2014 markeras och dess relation till COUNT(soh.OrderDate) .

Om du försöker använda COUNT(*) blir antalet beställningar för det datumet 1, vilket är fel. Det finns inga beställningar det datumet. Så när du använder COUNT() med en OUTTER JOIN, använd rätt kolumn för att räkna.

I vårt fall soh.OrderDate kan vara null eller inte. När den inte är null kommer COUNT() att inkludera raden i räkningen. COUNT(*) kommer att få det att räkna allt, inklusive nollvärdena. Och i slutändan, fel resultat.

The OUTER JOIN Takeaways

Låt oss sammanfatta punkterna:

  • OUTTER JOIN kan returnera både inre rader och yttre rader. Inre rader är resultatet som liknar INNER JOINs resultat. Yttre rader är de icke-nullvärden med sina nollmotsvarigheter baserat på kopplingsvillkoret.
  • OUTTER JOIN kan vara LEFT, RIGHT eller FULL. Vi hade exempel för var och en.
  • De yttre raderna som returneras av OUTER JOIN kan användas på en mängd olika praktiska sätt. Vi hade idéer om när du kan använda det här.
  • Vi hade också reservationer när det gäller att använda OUTER JOIN. Tänk på de 3 punkterna ovan för att undvika buggar och prestandaproblem.

Den sista delen av denna serie kommer att diskutera CROSS JOIN. Så, tills dess. Och om du gillar det här inlägget, dela lite kärlek genom att klicka på knapparna för sociala medier. Lycka till med kodningen!


  1. Apache NiFi

  2. Återställ en SQLite-databas

  3. hur gör man anslutningspooling i java?

  4. Är främmande nycklar verkligen nödvändiga i en databasdesign?