Ydre sammenføjning er i centrum i dag. Og dette er del 2 af din ultimative guide til SQL joins. Hvis du gik glip af del 1, er linket her.
Udefra er ydre det modsatte af indre. Men hvis du betragter den ydre sammenføjning på denne måde, vil du blive forvirret. For at toppe det behøver du ikke at inkludere ordet ydre i din syntaks eksplicit. Det er valgfrit!
Men før vi dykker ind, lad os diskutere nuller vedrørende ydre sammenføjninger.
Nuller og YDRE JOIN
Når du forbinder 2 tabeller, kan en af værdierne fra begge tabeller være nul. For INNER JOINs vil registreringer med nuller ikke matche, og de vil blive kasseret og vises ikke i resultatsættet. Hvis du vil have de optegnelser, der ikke stemmer overens, er din eneste mulighed OUTER JOIN.
Går tilbage til antonymer, er det ikke det modsatte af INNER JOINs? Ikke helt, som du vil se i næste afsnit.
Alt om SQL Server OUTER JOIN
Forståelse af ydre sammenføjninger starter med outputtet. Her er en komplet liste over, hvad du kan forvente:
- Alle poster, der matcher joinbetingelsen eller prædikatet. Det er udtrykket lige efter nøgleordet ON, ligesom INNER JOIN-outputtet. Vi omtaler dette problem som den indre række .
- Ikke-NULL-værdier fra venstre tabel med nul-modstykkerne fra højre bord. Vi omtaler dette problem som ydre rækker .
- Ikke-NULL-værdier fra højre tabel med nul-modstykkerne fra venstre bord. Dette er en anden form for ydre rækker.
- Til sidst kan det være en kombination af alle de ting, der er beskrevet ovenfor.
Med den liste kan vi sige, at YDRE JOIN returnerer indre og ydre rækker .
- Indre – fordi de nøjagtige resultater af INNER JOIN kan være returneret.
- Ydre – fordi de ydre rækker også kan være returneret.
Det er forskellen fra INNER JOIN.
INDERNE JOINS TILBAGE KUN INDERSTE RÆKKER. YDRE JOINS KAN RETURNERE BÅDE INDRE OG YDRE RÆKKER
Bemærk, at jeg brugte "kan være" og "kan også være." Det afhænger af din WHERE-sætning (eller hvis du nogensinde inkluderer en WHERE-sætning), om den returnerer både indre og/eller ydre rækker.
Men ud fra en SELECT-sætning, hvordan kan du bestemme hvilken er den venstre eller højre tabel ? Godt spørgsmål!
Hvordan ved man, hvilken der er venstre eller højre tabel i en sammenføjning?
Vi kan besvare dette spørgsmål med eksempler:
VÆLG *FRA Tabel1 til VENSTRE YDRE JOIN Tabel2 b på a.column1 =b.column1
Fra eksemplet ovenfor, Tabel1 er den venstre tabel og Tabel2 er det rigtige bord. Lad os nu have et andet eksempel. Denne gang er det en simpel multi-join.
VÆLG *FRA Tabel1 aLEFT OUTER JOIN Tabel2 b på a.column1 =b.column1LEFT YDRE JOIN Tabel3 c på b.column2 =c.column1
I dette tilfælde, for at vide, hvad der er venstre eller højre, skal du huske, at en join fungerer på 2 borde.
Tabel 1 er stadig den venstre tabel, og Tabel2 er det rigtige bord. Dette refererer til at forbinde 2 tabeller:Tabel1 og Tabel 2 . Hvad med at deltage i Tabel2 og Tabel 3 ? Tabel 2 bliver den venstre tabel, og Tabel3 er det rigtige bord.
Hvis vi tilføjer en fjerde tabel, Tabel3 bliver den venstre tabel, og Tabel4 er det rigtige bord. Men det slutter ikke der. Vi kan slutte et andet bord til Tabel1 . Her er et eksempel:
VÆLG *FRA Tabel1 aLEFT YDRE JOIN Tabel2 b på a.column1 =b.column1LEFT YDRE JOIN Tabel3 c på b.column2 =c.column1LEFT YDRE JOIN Tabel4 d på c.column1 =d.column2LEFT OUTER5 JOIN e on a.column2 =e.column1
Tabel 1 er den venstre tabel og Tabel5 er det rigtige bord. Du kan også gøre det samme med de andre tabeller.
Okay, lad os gå tilbage til listen over forventede output ovenfor. Vi kan også udlede de ydre sammenføjningstyper fra disse.
Typer af ydre samlinger
Der er 3 typer baseret på OUTER JOIN-udgangene.
VENSTRE YDRE JOIN (LEFT JOIN)
LEFT JOIN returnerer indre rækker + Ikke-NULL-værdier fra venstre bord med det rigtige bords null modstykker. Derfor er det LEFT JOIN, fordi den venstre tabel er den dominerende af de to tabeller i joinforbindelsen, der ikke har nulværdier.
VENSTRE YDRE JOIN EKSEMPEL 1
-- Returner alle kunde-ID'er med ordrer og ingen ordreUSE AdventureWorksGOSELECT c.CustomerID,soh.OrderDateFROM Sales.Customer CLEFT YDRE JOIN Sales.SalesOrderHeader soh ON c.CustomerID =soh.CustomerID
I eksemplet ovenfor, Kunden er den venstre tabel og SalesOrderHeader er det rigtige bord. Resultatet af forespørgslen er 32.166 poster – det omfatter både indvendige og ydre rækker. Du kan se en del af det i figur 1:
Antag, at vi kun ønsker at returnere de yderste rækker eller kunderne uden ordrer. For at gøre det skal du tilføje en WHERE-sætning for kun at inkludere rækker med null fra SalesOrderHeader .
VÆLG c.CustomerID,soh.OrderDateFROM Sales.Customer CLEFT YDER JOIN Sales.SalesOrderHeader soh ON c.CustomerID =soh.CustomerIDWHERE soh.SalesOrderID IS NULL
Det resultatsæt, jeg fik, er 701 rekorder . Alle kan lide null OrderDate fra figur 1.
Hvis jeg kun får de inderste rækker, vil resultatet være 31.465 poster . Jeg kan gøre det ved at ændre WHERE-sætningen til at inkludere disse SalesOrderID'er som ikke er nul. Eller jeg kan ændre joinforbindelsen til en INNER JOIN og fjerne WHERE-klausulen.
Lad os opsummere posterne for at se, om det tjekker ud fra outputtet fra det første eksempel uden WHERE-sætningen.
Indre rækker | Ydre rækker | Rækker i alt |
31.465 poster | 701 poster | 32.166 poster |
Fra de samlede rækker ovenfor med 32.166 poster, kan du se, at den tjekker ud med de første eksempelresultater. Dette viser også, hvordan LEFT OUTER JOIN fungerer.
VENSTRE YDRE JOIN EKSEMPEL 2
Denne gang er eksemplet en multi-join. Læg også mærke til, at vi fjerner nøgleordet YDRE.
-- vis personerne med og uden adresser fra AdventureWorksUSE AdventureWorksGOSELECT P.FirstName,P.MiddleName,P.LastName,a.AddressLine1,a.AddressLine2,a.City,adt.Name AS AddressTypeFROM Person.Person PLEFT JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID =bea.BusinessEntityIDLEFT JOIN Person.Address a ON bea.AddressID =a.AddressIDLEFT JOIN person.AddressType adt ON bea.AddressTypeID =adt.AddressTyp>
e
Det genererede 19.996 poster. Du kan se den del af outputtet i figur 2 nedenfor. Posterne med null AddressLine1 er yderste rækker. Over den er inderste rækker.
RIGHT YDRE JOIN (RIGHT JOIN)
RIGHT JOIN returnerer indre rækker + Ikke-NULL værdier fra højre bord med venstre bords nul-modstykker.
HØJRE YDRE JOIN EKSEMPEL 1
-- Fra produktanmeldelserne skal du returnere produkterne uden produktanmeldelser BRUG AdventureWorksGOSELECTP.NameFRA Production.ProductReview prRIGHT YDRE JOIN Production.Product p ON pr.ProductID =p.ProductIDWHERE pr.ProductReviewID IS NULL
Figur 3 viser 10 af 501 poster i resultatsættet.
I ovenstående eksempel, ProductReview er den venstre tabel og Produktet er det rigtige bord. Da dette er en RIGHT OUTER JOIN, har vi til hensigt at inkludere Non-NULL-værdierne fra den højre tabel.
Men valget mellem LEFT JOIN eller RIGHT JOIN afhænger af dig. Hvorfor? Fordi du kan udtrykke forespørgslen, uanset om det er LEFT eller RIGHT JOIN, og få de samme resultater. Lad os prøve det med et LEFT JOIN.
-- returner produkterne uden produktanmeldelser ved at bruge LEFT OUTER JOINUSE AdventureWorksGOSELECTP.NameFROM Production.Product pLEFT YDRE JOIN Production.ProductReview pr ON pr.ProductID =p.ProductIDWHERE pr.ProductReviewID IS NULL
Prøv at udføre ovenstående, og du vil få det samme resultat som i figur 3. Men tror du, at Query Optimizer vil behandle dem anderledes? Lad os finde ud af det i udførelsesplanen for begge i figur 4.
Hvis du er ny til dette, er der et par overraskelser i udførelsesplanen.
- Diagrammerne ser ens ud, og de er:prøv en Sammenlign Showplan , og du vil se den samme QueryPlanHash .
- Bemærk det øverste diagram med en Merge join. Vi brugte en RIGHT OUTER JOIN, men SQL Server ændrede den til LEFT OUTER JOIN. Det skiftede også venstre og højre tabeller. Det gør det lig med den anden forespørgsel med LEFT JOIN.
Som du ser nu, er resultaterne de samme. Så vælg, hvilken af OUTER JOINs, der vil være mere praktisk.
Hvorfor ændrede SQL Server RIGHT JOIN til LEFT JOIN?
Databasemotoren behøver ikke at følge den måde, du udtrykker de logiske joinforbindelser på. Så længe det kan producere korrekte resultater på den hurtigste måde, det tror muligt, vil det foretage ændringer. Selv genveje.
Konkluder ikke, at RGHT JOIN er dårligt, og LEFT JOIN er godt.
HØJRE YDRE JOIN EKSEMPEL 2
Tag et kig på nedenstående eksempel:
-- Hent de ikke-tildelte adresser og adressetyperne uden adresseVÆLG P.FirstName,P.MiddleName,P.LastName,a.AddressLine1,a.AddressLine2,a.City,adt.Name AS AddressTypeFROM Person. Person RIGHT JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID =bea.BusinessEntityIDRIGHT JOIN Person.Address a ON bea.AddressID =a.AddressIDRIGHT JOIN person.AddressType adt ON bea.AddressTypeID =adt.AddressENNUTypeID =adt.AddressENuTyp>
Der er 2 ting, du kan få fra denne forespørgsel, som du kan se i figur 5 nedenfor:
Forespørgselsresultaterne viser følgende:
- De ikke-tildelte adresser – disse poster er dem med null-navne.
- Adressetyper uden adresser. Arkiv-, fakturerings- og primære adressetyper har ingen tilsvarende adresser. De er fra post 817 til 819.
FULD YDRE JOIN (FULD JOIN)
FULD JOIN returnerer en kombination af indre rækker og ydre rækker, venstre og højre.
-- Hent personer med og uden adresser, ikke-tildelte adresser og adressetyper uden adresserVÆLG P.Fornavn,P.Mellemnavn,P.Efternavn,a.AddressLine1,a.AddressLine2,a.City,adt.Name AS AddressTypeFROM Person.Person pFULL JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID =bea.BusinessEntityIDFULL JOIN Person.Address a ON bea.AddressID =a.AddressIDFULL JOIN person.AddressType Address ON beaTypAddressID.
Resultatsættet indeholder 20.815 poster. Ligesom hvad du ville forvente, er det et samlet antal poster fra resultatsættet INNER JOIN, LEFT JOIN og RIGHT JOIN.
LEFT og RIGHT JOIN inkluderer en WHERE-sætning for kun at vise resultaterne med null i enten venstre eller højre tabeller.
INDRE JOIN VENSTRE JOIN
(HVOR a.AddressID ER NULL) HØJRE JOIN
(HVOR P.BusinessEntityID ER NULL) TOTAL (Samme som FULD JOIN) 18.798 poster 1.198 poster 819 poster 20.815 poster
Bemærk, at FULD JOIN kan producere et enormt resultatsæt fra store borde. Så brug det kun, når du kun har brug for det.
Praktisk brug af OUTER JOIN
Hvis du stadig tøver, når du kan og bør bruge OUTER JOIN, er her nogle ideer.
Ydre forbinder, der udsender både indre og ydre rækker
Eksempler kan være:
- Alfabetisk liste over betalte og ubetalte kundeordrer.
- Alfabetisk liste over medarbejdere med forsinkelse eller ingen forsinkelse.
- En liste over forsikringstagere, der har fornyet og ikke fornyet deres seneste forsikringer.
Ydre sammenføjninger, der kun udsender ydre rækker
Eksempler omfatter:
- alfabetisk liste over medarbejdere uden forsinkelsesrekord for nulforsinkelsesprisen
- liste over områder uden kunder
- liste over salgsagenter uden salg af et bestemt produkt
- få resultater fra manglende værdier, såsom datoer uden salgsordrer i en given periode (eksempel nedenfor)
- noder uden underordnede i et forældre-barn-forhold (eksempel nedenfor)
Få resultater fra manglende værdier
Antag, at du skal lave en rapport. Denne rapport skal vise antallet af dage for hver måned i en given periode, hvor der ikke var nogen ordrer. SalesOrderHeader i AdventureWorks indeholder Ordredatoer , men de har ikke datoer uden ordrer. Hvad kan du gøre?
1. Opret en tabel over alle datoer i en periode
Et eksempelscript nedenfor vil oprette en tabel over datoer for hele 2014:
DECLARE @StartDate date ='20140101', @EndDate date ='20141231';CREATE TABLE dbo.Dates( d DATE NOT null PRIMÆR NØGLE)MENS @StartDate <=@EndDateBEGIN INDSÆT Datoer([d]) VÆLG @StartDate; SET @StartDate =DATEADD(DAY, 1, @StartDate);ENDSELECT d FROM Dates BESTIL AF [d];
2. Brug LEFT JOIN til at udskrive dage uden ordrer
VÆLG MÅNED(d.d) AS [måned],ÅR(d.d) AS [år],ANTAL(*) AS NoOrderDaysFROM Datoer DLEFT JOIN Sales.SalesOrderHeader soh ON d.d =soh.OrderDateWHERE soh.OrderDate IS NULLGROUP BY ÅR(d.d), MÅNED(d.d) BESTIL EFTER [år], [måned]
Koden ovenfor tæller antallet af dage, hvor der ikke er foretaget nogen ordrer. SalesOrderHeader indeholder datoerne med bestillinger. Så nuller returneret i joinforbindelsen tæller som dage uden ordrer.
I mellemtiden, hvis du vil vide de nøjagtige datoer, kan du fjerne optællingen og grupperingen.
VÆLG d.d,soh.OrderDateFROM Dates DLEFT JOIN Sales.SalesOrderHeader soh ON d.d =soh.OrderDateWHERE soh.OrderDate IS NULL
Eller hvis du vil tælle ordrer i en given periode og se, hvilken dato der har nul ordrer, gør du sådan her:
SELECT DISTINCT D.d AS SalesDate,COUNT(soh.OrderDate) AS NoOfOrdersFROM Dates DLEFT JOIN Sales.SalesOrderHeader soh ON d.d =soh.OrderDateWHERE d.d MELLEM '02/01/2014' 8/20142' GRUPPER EFTER d.dORDER BY d.d
Ovenstående kode tæller ordrer for februar 2014. Se resultatet:
Hvorfor fremhæver den 3. februar 2014? I mit eksemplar af AdventureWorks er der ingen salgsordrer for den dato.
Læg nu mærke til COUNT(soh.OrderDate) i koden. Senere vil vi afklare, hvorfor dette er så vigtigt.
Få barnløse noder i forældre-barn-forhold
Nogle gange har vi brug for at kende noderne uden barn i et forældre-barn-forhold.
Lad os bruge databasen, jeg har brugt i min artikel om HierarchyID. Du skal hente noder uden børn i en forældre-barn-relationstabel ved hjælp af en selv-join.
VÆLG r1.RankParentId,r1.Rank AS RankParent,r.RankIdFROM Ranger rRIGHT JOIN Ranger r1 PÅ r.RankParentId =r1.RankIdWHERE r.RankId er NULL
Forbehold ved brug af OUTER JOIN
Da en OUTTER JOIN kan returnere indre rækker som en INNER JOIN, kan den forvirre. Ydeevneproblemer kan også snige sig ind. Så bemærk de 3 punkter nedenfor (jeg vender tilbage til dem fra tid til anden – jeg bliver ikke yngre, så jeg glemmer det også).
Filtrering af den højre tabel i en LEFT JOIN med en ikke-nullværdi i WHERE-sætningen
Det kan være et problem, hvis du brugte en LEFT OUTER JOIN, men filtrerede den højre tabel med en ikke-nullværdi i WHERE-sætningen. Årsagen er, at det funktionelt bliver ækvivalent med en INNER JOIN. Overvej eksemplet nedenfor:
BRUG AdventureWorksGOSELECT P.FirstName,P.MiddleName,P.LastName,a.AddressLine1,a.AddressLine2,a.City,adt.Name AS AddressTypeFROM Person.Person VENSTRET JOIN Person.BusinessEntityAddress bea ON P.BusinessEntity =bea.BusinessEntityIDLEFT JOIN Person.Address a ON bea.AddressID =a.AddressIDLEFT JOIN person.AddressType adt ON bea.AddressTypeID =adt.AddressTypeIDWHERE bea.AddressTypeID =5
Fra koden ovenfor, lad os undersøge de 2 tabeller:Person og BusinessEntityAddress . Personen er den venstre tabel og BusinessEntityAddress er det rigtige bord.
LEFT JOIN bruges, så det antager et null BusinessEntityID et sted i BusinessEntityAddress . Læg her mærke til WHERE-klausulen. Den filtrerer den rigtige tabel med AddressTypeID =5. Det kasserer fuldstændigt alle ydre rækker i BusinessEntityAddress .
Dette kan enten være en af disse:
- Udvikleren tester noget i resultatet, men glemte at fjerne det.
- INNER JOIN var tilsigtet, men af en eller anden grund blev LEFT JOIN brugt.
- Udvikleren forstår ikke forskellen mellem LEFT JOIN og INNER JOIN. Han antager, at nogen af de 2 vil virke, og det gør ikke noget, fordi resultaterne er de samme i dette tilfælde.
Enhver af de 3 ovenstående er dårlige, men den tredje post har en anden implikation. Lad os sammenligne koden ovenfor med INNER JOIN-ækvivalenten:
VÆLG P.FirstName,P.MiddleName,P.LastName,a.AddressLine1,a.AddressLine2,a.City,adt.Name AS AddressTypeFROM Person.Person PINNER JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID =bea.BusinessEntityIDINNER JOIN Person.Address a ON bea.AddressID =a.AddressIDINNER JOIN person.AddressType adt ON bea.AddressTypeID =adt.AddressTypeIDWHERE bea.AddressTypeID =5
Det ligner den forrige kode bortset fra typen af joinforbindelse. Resultatet er også det samme, men du bør bemærke de logiske læsninger i STATISTICS IO:
I figur 7 er de første I/O-statistikker fra brugen af INNER JOIN. Det samlede antal logiske læsninger er 177. Den anden statistik er dog for LEFT JOIN med en højere logisk læsningsværdi på 223. Derfor vil forkert brug af LEFT JOIN i dette eksempel kræve flere sider eller ressourcer fra SQL Server. Derfor vil den køre langsommere.
Takeaway
Hvis du har til hensigt at udskrive indre rækker, skal du bruge INNER JOIN. Ellers må du ikke filtrere den højre tabel i en LEFT JOIN med en værdi, der ikke er nul. Hvis dette sker, ender du med en langsommere forespørgsel, end hvis du bruger INNER JOIN.
BONUSTIP :Denne situation sker også i en RIGHT JOIN, når den venstre tabel er filtreret med en ikke-nul værdi.
Ukorrekt brug af Join-typer i en Multi-Join
Antag, at vi ønsker at få alle leverandørerne og antallet af produktindkøbsordrer for hver. Her er koden:
BRUG AdventureWorksGOSELECT v.BusinessEntityID,v.Name AS Vendor,pod.ProductID,pod.OrderQtyFROM Purchasing.Vendor vLEFT JOIN Purchasing.PurchaseOrderHeader poh ON v.BusinessEntityID =poh.PendorIDLEOrdering.poh.VendorIDLEOrdering.poh.PurchaseOrderHeader PurchaseOrderID =pod.PurchaseOrderID
Ovenstående kode returnerer både leverandører med indkøbsordrer og dem uden. Figur 8 viser den faktiske udførelsesplan for ovenstående kode.
Når man tænker på, at hver indkøbsordre har en garanteret indkøbsordredetalje, ville en INNER JOIN være bedre. Men er det virkelig sådan?
Lad os først få den ændrede kode med INNER JOIN.
BRUG AdventureWorksGOSELECT v.BusinessEntityID,v.Name AS Vendor,pod.ProductID,pod.OrderQtyFROM Purchasing.Vendor vLEFT JOIN Purchasing.PurchaseOrderHeader poh ON v.BusinessEntityID =poh.PendorIDchaINOrderh PurchaseOrderID =pod.PurchaseOrderID
Husk, kravet ovenfor siger "alle" leverandører. Da vi brugte LEFT JOIN i den forrige kode, får vi leverandører uden indkøbsordrer returneret. Det er på grund af det null PurchaseOrderID .
Ændring af joinforbindelsen til en INNER JOIN vil kassere alle de null PurchaseOrderID'er. Det vil også annullere alle null VendorIDs fra leverandøren bord. Faktisk bliver det en INNER JOIN.
Er det en korrekt antagelse? Udførelsesplanen vil afsløre svaret:
Som du kan se, blev alle tabeller behandlet ved hjælp af INNER JOIN. Derfor er vores antagelse korrekt. Men for det værste er resultatsættet nu forkert, fordi leverandørerne uden ordrer ikke var inkluderet.
Takeaway
Ligesom i det foregående tilfælde, hvis du har til hensigt en INNER JOIN, skal du bruge den. Men du ved, hvad du skal gøre, hvis du støder på en situation som den her.
I dette tilfælde vil en INNER JOIN kassere alle ydre rækker op til den øverste tabel i relationen. Selvom dit andet medlem er et LEFT JOIN, er det lige meget. Det har vi bevist i udførelsesplanerne.
Forkert brug af COUNT() i Outer Joins
Kan du huske vores eksempelkode, der tæller antallet af ordrer pr. dato og resultatet i figur 6?
Her vil vi afklare, hvorfor 02/03/2014 er fremhævet, og dets relation til COUNT(soh.OrderDate) .
Hvis du prøver at bruge COUNT(*), bliver antallet af ordrer for den dato 1, hvilket er forkert. Der er ingen ordrer på den dato. Så når du bruger COUNT() med en OUTER JOIN, skal du bruge den korrekte kolonne til at tælle.
I vores tilfælde soh.OrderDate kan være nul eller ej. Når den ikke er nul, vil COUNT() inkludere rækken i optællingen. COUNT(*) vil få det til at tælle alt, inklusive nullerne. Og i sidste ende, forkerte resultater.
De ydre JOIN Takeaways
Lad os opsummere pointerne:
- YDRE JOIN kan returnere både indre rækker og ydre rækker. Indre rækker er resultatet, der ligner INNER JOINs resultat. Yderste rækker er de ikke-nul-værdier med deres null-modstykker baseret på sammenføjningsbetingelsen.
- YDRE JOIN kan være VENSTRE, HØJRE eller FULD. Vi havde eksempler til hver.
- De ydre rækker, der returneres af OUTER JOIN, kan bruges på en række praktiske måder. Vi havde ideer til, hvornår du kan bruge disse ting.
- Vi havde også forbehold ved at bruge OUTER JOIN. Vær opmærksom på de 3 punkter ovenfor for at undgå fejl og ydeevneproblemer.
Den sidste del af denne serie vil diskutere CROSS JOIN. Så indtil da. Og hvis du kan lide dette opslag, så del noget kærlighed ved at klikke på knapperne på de sociale medier. God kodning!