Bruger du SQL-underforespørgsler eller undgår du at bruge dem?
Lad os sige, at kredit- og inkassochefen beder dig om at liste navnene på personer, deres ubetalte saldi pr. måned og den aktuelle løbende saldo og vil have dig til at importere denne dataarray til Excel. Formålet er at analysere dataene og komme med et tilbud, der gør betalingerne lettere for at afbøde virkningerne af COVID19-pandemien.
Vælger du at bruge en forespørgsel og en indlejret underforespørgsel eller en joinforbindelse? Hvilken beslutning vil du tage?
SQL-underforespørgsler – hvad er de?
Før vi dykker ned i syntaks, ydeevnepåvirkning og advarsler, hvorfor så ikke definere en underforespørgsel først?
I de enkleste vendinger er en underforespørgsel en forespørgsel i en forespørgsel. Mens en forespørgsel, der inkorporerer en underforespørgsel, er den ydre forespørgsel, henviser vi til en underforespørgsel som den indre forespørgsel eller indre udvælgelse. Og parenteser omslutter en underforespørgsel, der ligner strukturen nedenfor:
VÆLG col1,col2,(subquery) som col3FROM table1[JOIN table2 ON table1.col1 =table2.col2]WHERE col1 (subquery)
Vi vil se på følgende punkter i dette indlæg:
- SQL-underforespørgselssyntaks afhængig af forskellige underforespørgselstyper og operatorer.
- Hvornår og i hvilken slags udsagn kan man bruge en underforespørgsel.
- Ydeevnekonsekvenser ift. JOINs .
- Almindelige forbehold ved brug af SQL-underforespørgsler.
Som det er sædvanligt, giver vi eksempler og illustrationer for at øge forståelsen. Men husk, at hovedfokus i dette indlæg er på underforespørgsler i SQL Server.
Lad os nu komme i gang.
Lav SQL-underforespørgsler, der er selvstændige eller korrelerede
For det første kategoriseres underforespørgsler baseret på deres afhængighed af den ydre forespørgsel.
Lad mig beskrive, hvad en selvstændig underforespørgsel er.
Selvstændige underforespørgsler (eller nogle gange omtalt som ikke-korrelerede eller simple underforespørgsler) er uafhængige af tabellerne i den ydre forespørgsel. Lad mig illustrere dette:
-- Få salgsordrer fra kunder fra det sydvestlige USA -- (TerritoryID =4)BRUG [AdventureWorks]GOSELECT kunde-ID, SalesOrderIDFROM Sales.SalesOrderHeader WHERE CustomerID IN (VÆLG [CustomerID] FRA [AdventureWorks].[Salg] .[Kunde] WHERE TerritoryID =4)
Som vist i ovenstående kode har underforespørgslen (omsluttet i parentes nedenfor) ingen referencer til nogen kolonne i den ydre forespørgsel. Derudover kan du fremhæve underforespørgslen i SQL Server Management Studio og udføre den uden at få runtime-fejl.
Hvilket igen fører til lettere fejlfinding af selvstændige underforespørgsler.
Den næste ting at overveje er korrelerede underforespørgsler. Sammenlignet med dens selvstændige modstykke har denne mindst én kolonne, der refereres til fra den ydre forespørgsel. For at præcisere vil jeg give et eksempel:
BRUG [AdventureWorks]GOSELECT DISTINCT a.LastName, a.FirstName, b.BusinessEntityIDFROM Person.Person AS pJOIN HumanResources.Employee AS e ON p.BusinessEntityID =e.BusinessEntityIDWHERE 1202Sa (SELECT) .SalesPersonQuotaHistory spq WHERE p.BusinessEntityID =spq.BusinessEntityID)
Var du opmærksom nok til at bemærke henvisningen til BusinessEntityID fra Personen bord? Godt gået!
Når der henvises til en kolonne fra den ydre forespørgsel i underforespørgslen, bliver den en korreleret underforespørgsel. Endnu et punkt at overveje:Hvis du fremhæver en underforespørgsel og udfører den, vil der opstå en fejl.
Og ja, du har fuldstændig ret:dette gør korrelerede underforespørgsler ret sværere at fejlfinde.
Følg disse trin for at gøre fejlfinding mulig:
- isoler underforespørgslen.
- erstat referencen til den ydre forespørgsel med en konstant værdi.
Isolering af underforespørgslen til debugging vil få det til at se sådan ud:
VÆLG [SalesQuota] FRA Sales.SalesPersonQuotaHistory spq WHERE spq.BusinessEntityID =
Lad os nu grave lidt dybere ned i outputtet af underforespørgsler.
Lav SQL-underforespørgsler med 3 mulige returnerede værdier
Lad os først tænke på, hvilke returnerede værdier vi kan forvente fra SQL-underforespørgsler.
Faktisk er der 3 mulige udfald:
- En enkelt værdi
- Flere værdier
- Hele tabeller
Enkelt værdi
Lad os starte med output med enkelt værdi. Denne type underforespørgsel kan forekomme hvor som helst i den ydre forespørgsel, hvor der forventes et udtryk, f.eks. WHERE klausul.
-- Udskriv en enkelt værdi, som er den maksimale eller sidste TransactionIDUSE [AdventureWorks]GOSELECT TransactionID, ProductID, TransactionDate, QuantityFROM Production.TransactionHistoryWHERE TransactionID =(SELECT MAX(t.TransactionID) FROM Production.TransactionHistory t)
Når du bruger en MAX () funktion, henter du en enkelt værdi. Det er præcis, hvad der skete med vores underforespørgsel ovenfor. Ved at bruge den lige (= ) operatør fortæller SQL Server, at du forventer en enkelt værdi. En anden ting:hvis underforespørgslen returnerer flere værdier ved hjælp af lig (= )-operatør, får du en fejl svarende til nedenstående:
Besked 512, niveau 16, tilstand 1, linje 20 Underforespørgsel returnerede mere end 1 værdi. Dette er ikke tilladt, når underforespørgslen følger efter =, !=, <, <=,>,>=eller når underforespørgslen bruges som et udtryk.
Flere værdier
Dernæst undersøger vi output med flere værdier. Denne form for underforespørgsel returnerer en liste over værdier med en enkelt kolonne. Derudover kan operatører som IN og IKKE I vil forvente en eller flere værdier.
-- Udskriv flere værdier, som er en liste over kunder med efternavne, der --- starter med 'I'USE [AdventureWorks]GOSELECT [SalesOrderID], [OrderDate], [ShipDate], [CustomerID]FROM Sales .SalesOrderHeaderWHERE [CustomerID] IN (VÆLG c.[CustomerID] FRA Sales.Customer CINNER JOIN Person.Person p ON c.PersonID =p.BusinessEntityIDWHERE p.efternavn LIKE N'I%' AND p.PerSC'Type=' /kode> Hele tabelværdier
Og sidst, men ikke mindst, hvorfor ikke dykke ned i hele tabeloutput.
-- Udskriv en tabel med værdier baseret på salgsordrer. BRUG [AdventureWorks]GOSELECT [ShipYear],COUNT(DISTINCT [CustomerID]) AS CustomerCountFROM (SELECT YEAR([ShipDate]) AS [ShipYear], [CustomerID] FRA Sales.SalesOrderHeader) SOM Forsendelser GRUPPE AF [ShipYear]ORDER BY [ShipYear]
Har du lagt mærke til FRA klausul?
I stedet for at bruge en tabel, brugte den en underforespørgsel. Dette kaldes en afledt tabel eller en tabelunderforespørgsel.
Og nu, lad mig præsentere dig nogle grundregler, når du bruger denne form for forespørgsel:
- Alle kolonner i underforespørgslen skal have unikke navne. Ligesom en fysisk tabel bør en afledt tabel have unikke kolonnenavne.
- BEstil efter er ikke tilladt, medmindre TOP er også specificeret. Det skyldes, at den afledte tabel repræsenterer en relationstabel, hvor rækker ikke har nogen defineret rækkefølge.
I dette tilfælde har en afledt tabel fordelene ved en fysisk tabel. Det er derfor, vi i vores eksempel kan bruge COUNT () i en af kolonnerne i den afledte tabel.
Det handler om alt hvad angår subforespørgselsoutput. Men før vi kommer videre, har du måske bemærket, at logikken bag eksemplet for flere værdier og andre også kan gøres ved at bruge en JOIN .
-- Udskriv flere værdier, som er en liste over kunder med efternavne, der starter med 'I'USE [AdventureWorks]GOSELECT o.[SalesOrderID], o.[OrderDate], o.[ShipDate], o. [CustomerID]FROM Sales.SalesOrderHeader oINNER JOIN Sales.Customer c on o.CustomerID =c.CustomerIDINNER JOIN Person.Person p ON c.PersonID =p.BusinessEntityIDWHERE p.LastName LIKE N'Person AND p. '
Faktisk vil outputtet være det samme. Men hvilken klarer sig bedst?
Før vi kommer ind på det, lad mig fortælle dig, at jeg har dedikeret et afsnit til dette hotte emne. Vi vil undersøge det med komplette udførelsesplaner og se på illustrationer.
Så bær over med mig et øjeblik. Lad os diskutere en anden måde at placere dine underforespørgsler på.
Andre udsagn, hvor du kan bruge SQL-underforespørgsler
Indtil videre har vi brugt SQL-underforespørgsler på SELECT udsagn. Og sagen er, at du kan nyde fordelene ved underforespørgsler på INSERT , OPDATERING og SLET sætninger eller i enhver T-SQL-sætning, der danner et udtryk.
Så lad os tage et kig på en række flere eksempler.
Brug af SQL-underforespørgsler i UPDATE-sætninger
Det er nemt nok at inkludere underforespørgsler i OPDATERING udsagn. Hvorfor ikke tjekke dette eksempel ud?
-- I produktbeholdningen skal du overføre alle produkter fra leverandør 1602 til ------ lokation 6USE [AdventureWorks]GOUPDATE [Produktion].[ProductInventory]SET LocationID =6WHERE ProductID IN (VÆLG produkt-ID FRA indkøb .ProductVendor WHERE BusinessEntityID =1602)GO
Fik du øje på, hvad vi lavede der?
Sagen er, at du kan sætte underforespørgsler i Hvor klausul af en OPDATERING erklæring.
Da vi ikke har det i eksemplet, kan du også bruge en underforespørgsel til SET klausul som SET kolonne =(underforespørgsel) . Men vær advaret:den skal udsende en enkelt værdi, fordi der ellers opstår en fejl.
Hvad gør vi så?
Brug af SQL-underforespørgsler i INSERT-sætninger
Som du allerede ved, kan du indsætte poster i en tabel ved hjælp af en SELECT udmelding. Jeg er sikker på, at du har en idé om, hvad underforespørgselsstrukturen vil være, men lad os demonstrere dette med et eksempel:
-- Påtving en lønstigning for alle medarbejdere i afdelings-ID 6 -- (Forskning og udvikling) med 10 (tror jeg dollars) -- med virkning fra 1. juni 2020 BRUG [AdventureWorks]GOINSERT INTO [HumanResources].[ EmployeePayHistory]([BusinessEntityID],[RateChangeDate],[Rate],[PayFrequency],[ModifiedDate])SELECTa.BusinessEntityID,'06/01/2020' som RateChangeDate,(SELECT MAX(b.Rate) FROM] [HumanResources]. [EmployeePayHistory] b WHERE a.BusinessEntityID =b.BusinessEntityID) + 10 som NewRate,2 som PayFrequency,getdate() som ModifiedDateFROM [HumanResources].[EmployeeDepartmentHistory] aWHERE a.DepartmentDateID =.6Department(Dato) FROM HumanResources.EmployeeDepartmentHistory c WHERE c.BusinessEntityID =a.BusinessEntityID)
Så hvad ser vi på her?
- Den første underforespørgsel henter den sidste lønsats for en medarbejder, før du tilføjer de yderligere 10.
- Den anden underforespørgsel får den sidste lønpost for medarbejderen.
- Til sidst resultatet af SELECT er indsat i EmployeePayHistory tabel.
I andre T-SQL-erklæringer
Bortset fra SELECT , INDSÆT , OPDATERING og SLET , kan du også bruge SQL-underforespørgsler i følgende:
Variabelerklæringer eller SET-sætninger i lagrede procedurer og funktioner
Lad mig præcisere ved at bruge dette eksempel:
DECLARE @maxTransId int =(SELECT MAX(TransactionID) FROM Production.TransactionHistory)
Alternativt kan du gøre dette på følgende måde:
DECLARE @maxTransId intSET @maxTransId =(VÆLG MAX(TransactionID) FROM Production.TransactionHistory)
I betingede udtryk
Hvorfor tager du ikke et kig på dette eksempel:
HVIS EKSISTERER(VÆLG [Navn] FRA sys.tables, hvor [Navn] ='Mine leverandører') START SLIP TABEL Mine leverandørerEND
Bortset fra det kan vi gøre det sådan her:
IF (SELECT count(*) FROM MyVendors)> 0BEGIN -- indsæt kode her END
Foretag SQL-underforespørgsler med sammenlignings- eller logiske operatører
Indtil videre har vi set de ligeværdige (= ) operatør og IN operatør. Men der er meget mere at udforske.
Brug af sammenligningsoperatører
Når en sammenligningsoperator som =, <,>, <>,>=eller <=bruges med en underforespørgsel, skal underforespørgslen returnere en enkelt værdi. Desuden opstår der en fejl, hvis underforespørgslen returnerer flere værdier.
Eksemplet nedenfor vil generere en runtime-fejl.
BRUG [AdventureWorks]GOSELECT b.LastName, b.FirstName, b.MiddleName, a.JobTitle, a.BusinessEntityIDFROM HumanResources.Employee aINNER JOIN Person.Person b på a.BusinessEntityID =b.Business JOINIDINNER HumanResources. EmployeeDepartmentHistory c på a.BusinessEntityID =c.BusinessEntityIDWHERE c.DepartmentID =6 og StartDate =(SELECT d.StartDate FROM HumanResources.EmployeeDepartmentHistory d WHERE d.BusinessEntityID =a.BusinessEntityID)
Ved du, hvad der er galt i ovenstående kode?
Først og fremmest bruger koden operatoren equals (=) med underforespørgslen. Derudover returnerer underforespørgslen en liste over startdatoer.
For at løse problemet skal du få underforespørgslen til at bruge en funktion som MAX () i startdatokolonnen for at returnere en enkelt værdi.
Brug af logiske operatører
Brug af EXISTS eller NOT EXISTS
FINDER returnerer TRUE hvis underforespørgslen returnerer nogle rækker. Ellers returnerer den FALSK . I mellemtiden bruger du NOT FINDER vil returnere TRUE hvis der ikke er nogen rækker og FALSK , ellers.
Overvej eksemplet nedenfor:
HVIS EKSISTERER(VÆLG navn FRA sys.tables hvor navn ='Token')BEGIN SLIP TABLE TokenEND
Først, tillad mig at forklare. Ovenstående kode vil fjerne tabeltokenet, hvis det findes i sys.tables , hvilket betyder, hvis det findes i databasen. Et andet punkt:referencen til kolonnenavnet er irrelevant.
Hvorfor er det det?
Det viser sig, at databasemotoren kun behøver at få mindst 1 række ved hjælp af EXISTS . I vores eksempel, hvis underforespørgslen returnerer en række, vil tabellen blive slettet. På den anden side, hvis underforespørgslen ikke returnerede en enkelt række, vil de efterfølgende sætninger ikke blive udført.
Derfor er bekymringen for FINDER er kun rækker og ingen kolonner.
Derudover FINDER bruger logik med to værdier:TRUE eller FALSK . Der er ingen tilfælde, hvor det vil returnere NULL . Det samme sker, når du afviser FINDER ved hjælp af NOT .
Brug af IN eller NOT IN
En underforespørgsel introduceret med IN eller IKKE I vil returnere en liste med nul eller flere værdier. Og i modsætning til FINDER , er en gyldig kolonne med den relevante datatype påkrævet.
Lad mig præcisere dette med et andet eksempel:
-- Fra produktbeholdningen, udtræk de produkter, der er tilgængelige -- (Antal>0) -- bortset fra produkter fra leverandør 1676, og indfør en prisnedsættelse for --- hele juni måned 2020 -- Indsæt resultaterne i produktprishistorikken.BRUG [AdventureWorks]GOINSERT INTO [Produktion].[ProductListPriceHistory] ([ProductID] ,[StartDate],[EndDate] ,[ListPrice] ,[ModifiedDate])VÆLG et.ProduktID, '06/01/2020' som Startdato, '06/30/2020' som EndDate,a.ListPrice - 2 som ReducedListPrice,getdate() som ModifiedDateFROM [Produktion].[ProductListPriceHistory] aWHERE a.StartDate =(SELECT MAX(StartDate) ) FRA Production.ProductListPriceHistory WHERE ProductID =a.ProductID)AND a.ProductID IN (SELECT ProductID FROM Production.ProductInventory WHERE Quantity> 0)AND a.ProductID NOT IN (SELECT ProductID FROM [Purchasing].[ProductVendor] WHERE BusinessEntityID =1676
Som du kan se fra ovenstående kode, både IN og IKKE I operatører introduceres. Og i begge tilfælde vil rækker blive returneret. Hver række i den ydre forespørgsel vil blive matchet med resultatet af hver underforespørgsel for at få et produkt, der er til rådighed, og et produkt, der ikke er fra leverandør 1676.
Indlejring af SQL-underforespørgsler
Du kan indlejre underforespørgsler selv op til 32 niveauer. Ikke desto mindre afhænger denne funktion af serverens tilgængelige hukommelse og kompleksiteten af andre udtryk i forespørgslen.
Hvad er din holdning til dette?
Min erfaring er, at jeg ikke kan huske at have redet op til 4. Jeg bruger sjældent 2 eller 3 niveauer. Men det er kun mig og mine krav.
Hvad med et godt eksempel til at finde ud af dette:
-- List navnene på medarbejdere, der også er kunder.BRUG [AdventureWorks]GOSELECTLastName,FirstName,MiddleNameFROM Person.PersonWHERE BusinessEntityID IN (VÆLG BusinessEntityID FRA Sales.Customer WHERE BusinessEntityID IN (SELECT BusinessEntityID FROM HumanResources.Employee. ))
Som vi kan se i dette eksempel, nåede nesting 2 niveauer.
Er SQL-underforespørgsler dårlige for ydeevne?
I en nøddeskal:ja og nej. Det afhænger med andre ord.
Og glem ikke, dette er i forbindelse med SQL Server.
Til at begynde med kan mange T-SQL-sætninger, der bruger underforespørgsler alternativt omskrives ved hjælp af JOIN s. Og ydeevnen for begge er normalt den samme. På trods af det er der særlige tilfælde, hvor en joinforbindelse er hurtigere. Og der er tilfælde, hvor underforespørgslen fungerer hurtigere.
Eksempel 1
Lad os undersøge et eksempel på en underforespørgsel. Før du udfører dem, skal du trykke på Control-M eller aktiver Inkluder faktisk eksekveringsplan fra værktøjslinjen i SQL Server Management Studio.
BRUG [AdventureWorks]GOSELECT NameFROM Production.ProductWHERE ListPrice =SELECT ListPrice FROM Production.Product WHERE Name ='Touring End Caps')
Alternativt kan forespørgslen ovenfor omskrives ved hjælp af en joinforbindelse, der giver det samme resultat.
BRUG [AdventureWorks]GOSELECT Prd1.NameFROM Production.Product AS Prd1INNER JOIN Production.Product AS Prd2 ON (Prd1.ListPrice =Prd2.ListPrice)WHERE Prd2.Name ='Touring End Caps'
I sidste ende er resultatet for begge forespørgsler 200 rækker.
Ud over det kan du tjekke udførelsesplanen for begge udsagn.
Figur 1:Udførelsesplan ved hjælp af en underforespørgsel
Figur 2:Udførelsesplan ved hjælp af en Join
Hvad synes du? Er de praktisk talt ens? Bortset fra den faktiske forløbne tid for hver node, er alt andet grundlæggende det samme.
Men her er en anden måde at sammenligne det på bortset fra visuelle forskelle. Jeg foreslår, at du bruger Sammenlign Showplan .
Følg disse trin for at udføre det:
- Højreklik på udførelsesplanen for sætningen ved hjælp af underforespørgslen.
- Vælg Gem eksekveringsplan som .
- Navngiv filen subquery-execution-plan.sqlplan .
- Gå til udførelsesplanen for erklæringen ved hjælp af en join, og højreklik på den.
- Vælg Sammenlign Showplan .
- Vælg det filnavn, du gemte i #3.
Se nu dette for at få flere oplysninger om Sammenlign Showplan .
Du burde kunne se noget lignende dette:
Figur 3:Sammenlign Showplan for at bruge en joinforbindelse vs. at bruge en underforespørgsel
Læg mærke til lighederne:
- Estimerede rækker og omkostninger er de samme.
- QueryPlanHash er også det samme, hvilket betyder, at de har lignende udførelsesplaner.
Vær dog opmærksom på forskellene:
- Cacheplanens størrelse er større ved at bruge joinforbindelsen end ved at bruge underforespørgslen
- Kompilering af CPU og tid (i ms), inklusive hukommelsen i KB, der bruges til at parse, binde og optimere eksekveringsplanen, er højere ved brug af join end ved at bruge underforespørgslen
- CPU-tid og forløbet tid (i ms) til at eksekvere planen er lidt højere ved brug af joinforbindelsen vs. underforespørgslen
I dette eksempel er underforespørgslen en tik hurtigere end joinforbindelsen, selvom de resulterende rækker er de samme.
Eksempel 2
I det foregående eksempel brugte vi kun én tabel. I det følgende eksempel skal vi bruge 3 forskellige tabeller.
Lad os få dette til at ske:
-- Eksempel på underforespørgsel BRUG [AdventureWorks]GOSELECT [SalesOrderID], [OrderDate], [ShipDate], [CustomerID]FROM Sales.SalesOrderHeader HVOR [CustomerID] IN (VÆLG c.[CustomerID] FROM Sales.Customer c INNER JOIN Person.Person p ON c.PersonID =p.BusinessEntityID WHERE p.PersonType='SC')
-- Deltag i eksempelUSE [AdventureWorks]GOSELECT o.[SalesOrderID], o.[OrderDate], o.[ShipDate], o.[CustomerID]FROM Sales.SalesOrderHeader oINNER JOIN Sales.Customer c on o. CustomerID =c.CustomerIDINNER JOIN Person.Person p ON c.PersonID =p.BusinessEntityIDWHERE p.PersonType ='SC'
Begge forespørgsler udsender de samme 3806 rækker.
Lad os derefter se på deres udførelsesplaner:
Figur 4:Udførelsesplan for vores andet eksempel ved hjælp af en underforespørgsel
Figur 5:Udførelsesplan for vores andet eksempel ved hjælp af en join
Kan du se de 2 udførelsesplaner og finde nogen forskel på dem? Ved et blik ser de ens ud.
Men en mere omhyggelig undersøgelse med Sammenlign Showplan afslører, hvad der virkelig er indeni.
Figur 6:Detaljer om Sammenlign Showplan for det andet eksempel
Lad os starte med at analysere et par ligheder:
- Det lyserøde højdepunkt i udførelsesplanen afslører lignende handlinger for begge forespørgsler. Da den indre forespørgsel bruger en joinforbindelse i stedet for indlejrede underforespørgsler, er dette ganske forståeligt.
- De estimerede omkostninger til operatør og undertræ er de samme.
Lad os derefter tage et kig på forskellene:
- For det første tog kompileringen længere tid, når vi brugte joins. Du kan kontrollere det i Compile CPU og Compile Time. Men forespørgslen med en underforespørgsel tog en højere kompileringshukommelse i KB.
- Så er QueryPlanHash for begge forespørgsler forskellig, hvilket betyder, at de har en anden eksekveringsplan.
- Sidst er den forløbne tid og CPU-tid til at eksekvere planen hurtigere ved at bruge joinforbindelsen end at bruge en underforespørgsel.
Underforespørgsel vs. Deltag i Performance Takeaway
Du vil sandsynligvis stå over for for mange andre forespørgselsrelaterede problemer, som kan løses ved at bruge en joinforbindelse eller en underforespørgsel.
Men bundlinjen er, at en underforespørgsel ikke i sig selv er dårlig sammenlignet med joinforbindelser. Og der er ingen tommelfingerregel om, at i en bestemt situation er en joinforbindelse bedre end en underforespørgsel eller omvendt.
Så for at sikre dig, at du har det bedste valg, skal du tjekke udførelsesplanerne. Formålet med det er at få indsigt i, hvordan SQL Server vil behandle en bestemt forespørgsel.
Men hvis du vælger at bruge en underforespørgsel, skal du være opmærksom på, at der kan opstå problemer, som vil teste dine færdigheder.
Almindelige forbehold ved brug af SQL-underforespørgsler
Der er 2 almindelige problemer, der kan få dine forespørgsler til at opføre sig vildt, når du bruger SQL-underforespørgsler.
Smerten ved opløsning af kolonnenavne
Dette problem introducerer logiske fejl i dine forespørgsler, og de kan være meget vanskelige at finde. Et eksempel kan yderligere afklare dette problem.
Lad os starte med at oprette en tabel til demoformål og udfylde den med data.
BRUG [AdventureWorks]GO-- Opret tabellen til vores demonstration baseret på LeverandørerCREATE TABLE Purchasing.MyVendors(BusinessEntity_id int,AccountNumber nvarchar(15),Name nvarchar(50))GO-- Udfyld nogle data til vores ny tabelINDSÆT I Purchasing.MyVendorsSELECT BusinessEntityID, AccountNumber, Name FROM Purchasing.VendorWHERE BusinessEntityID IN (SELECT BusinessEntityID FROM Purchasing.ProductVendor)AND BusinessEntityID like '14%'GO
Nu hvor tabellen er sat, lad os affyre nogle underforespørgsler ved hjælp af den. Men før du udfører forespørgslen nedenfor, skal du huske, at de leverandør-id'er, vi brugte fra den forrige kode, starter med '14'.
SELECT b.Name, b.ListPrice, a.BusinessEntityIDFROM Purchasing.ProductVendor aINNER JOIN Production.Product b on a.ProductID =b.ProductIDWHERE a.BusinessEntityID IN (SELECT BusinessEntityID FROM Purchasing.MyVendors)
Ovenstående kode kører uden fejl, som du kan se nedenfor. Under alle omstændigheder skal du være opmærksom på listen over BusinessEntityID'er .
Figur 7:Forretningsenheds-id'er for resultatsættet er inkonsistente med registreringerne i MyVendors-tabellen
Fik vi ikke indsat data med BusinessEntityID begynder med '14'? Hvad er der så i vejen? Faktisk kan vi se BusinessEntityID'er der starter med '15' og '16'. Hvor kom disse fra?
Faktisk viste forespørgslen alle data fra ProductVendor tabel.
I så fald tror du måske, at et alias vil løse dette problem, så det refererer til MyVendors tabel ligesom nedenstående:
Figur 8:Tilføjelse af et alias til BusinessEntityID'et resulterer i en fejl
Bortset fra at nu dukkede det virkelige problem op på grund af en runtime-fejl.
Tjek MyVendors tabel igen, og du vil se det i stedet for BusinessEntityID , skal kolonnenavnet være BusinessEntity_id (med en understregning).
Brug af det korrekte kolonnenavn vil derfor endelig løse dette problem, som du kan se nedenfor:
Figur 9:Ændring af underforespørgslen med det korrekte kolonnenavn løste problemet
Som du kan se ovenfor, kan vi nu observere BusinessEntityID'er starter med '14' ligesom vi tidligere har forventet.
Men du undrer dig måske: hvorfor i alverden tillod SQL Server at køre forespørgslen med succes i første omgang?
Her er kickeren:Opløsningen af kolonnenavne uden alias fungerer i forbindelse med underforespørgslen fra i sig selv at gå ud til den ydre forespørgsel. Det er derfor henvisningen til BusinessEntityID inde i underforespørgslen udløste ikke en fejl, fordi den findes uden for underforespørgslen – i ProductVendor tabel.
Med andre ord leder SQL Server efter den ikke-aliasede kolonne BusinessEntityID i MyVendors bord. Da den ikke er der, kiggede den udenfor og fandt den i ProductVendor bord. Skørt, ikke?
Du kan sige, at det er en fejl i SQL Server, men faktisk er det designet i SQL-standarden, og Microsoft fulgte det.
Okay, det er klart, vi kan ikke gøre noget ved standarden, men hvordan kan vi undgå at løbe ind i en fejl?
- Først skal du præfikse kolonnenavnene med tabelnavnet eller bruge et alias. Med andre ord, undgå tabelnavne uden præfiks eller ikke-alias.
- For det andet skal du have en ensartet navngivning af kolonner. Undgå at have begge BusinessEntityID og BusinessEntity_id , for eksempel.
Lyder godt? Ja, dette bringer noget fornuft ind i situationen.
Men dette er ikke enden på det.
Crazy NULLs
Som jeg nævnte, er der mere at dække. T-SQL bruger 3-værdi logik på grund af dens understøttelse af NULL . Og NULL kan næsten gøre os vanvittige, når vi bruger SQL-underforespørgsler med NOT IN .
Lad mig starte med at introducere dette eksempel:
SELECT b.Name, b.ListPrice, a.BusinessEntityIDFROM Purchasing.ProductVendor aINNER JOIN Production.Product b on a.ProductID =b.ProductIDWHERE a.BusinessEntityID NOT IN (SELECT c.BusinessEntity_id FROM Purchasing c.MyVendorsing )
Outputtet af forespørgslen fører os til en liste over produkter, der ikke er i MyVendors tabel., som vist nedenfor:
Figur 10:Outputtet af prøveforespørgslen ved brug af NOT IN
Antag nu, at nogen utilsigtet har indsat en post i MyVendors tabel med en NULL BusinessEntity_id . Hvad skal vi gøre ved det?
Figur 11:Resultatsættet bliver tomt, når et NULL BusinessEntity_id indsættes i MyVendors
Hvor blev alle data af?
Ser du, IKKE operatør afviste IN prædikat. Så IKKE SAND bliver nu FALSK . Men IKKE NULL er Ukendt. Det fik filteret til at kassere de rækker, der er UKENDTE, og det er synderen.
For at sikre, at dette ikke sker for dig:
- Før enten tabelkolonnen til at afvise NULL hvis data ikke skulle være sådan.
- Eller tilføj kolonnenavnet ER IKKE NULL til dit Hvor klausul. I vores tilfælde er underforespørgslen som følger:
SELECT b.Name, b.ListPrice, a.BusinessEntityIDFROM Purchasing.ProductVendor aINNER JOIN Production.Product b on a.ProductID =b.ProductIDWHERE a.BusinessEntityID NOT IN (SELECT c.BusinessEntity_id FROM Purchasing c.MyVendorsing WHERE c.BusinessEntity_id IS NOT NULL)
Takeaways
Vi har talt temmelig meget om underforespørgsler, og tiden er inde til at give de vigtigste takeaways af dette indlæg i form af en opsummeret liste:
En underforespørgsel:
- er en forespørgsel i en forespørgsel.
- er omgivet af parentes.
- kan erstatte et udtryk hvor som helst.
- kan bruges i SELECT , INDSÆT , OPDATERING , SLET, eller andre T-SQL-sætninger.
- kan være selvstændig eller korreleret.
- udskriver enkelt-, multiple- eller tabelværdier.
- fungerer på sammenligningsoperatorer som =, <>,>, <,>=, <=og logiske operatorer som IN /IKKE I og FINDER /FINDER IKKE .
- er ikke dårligt eller ondt. Det kan fungere bedre eller dårligere end JOIN s afhængig af en situation. Så tag mit råd og tjek altid udførelsesplanerne.
- kan have grim adfærd på NULL s, når det bruges sammen med NOT IN , og når en kolonne ikke er eksplicit identificeret med en tabel eller tabelalias.
Get familiarized with several additional references for your reading pleasure:
- Discussion of Subqueries from Microsoft.
- IN (Transact-SQL)
- EXISTS (Transact-SQL)
- ALL (Transact-SQL)
- SOME | ANY (Transact-SQL)
- Comparison Operators