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

NULL-kompleksiteter – del 2

Denne artikel er den anden i en serie om NULL-kompleksiteter. I sidste måned introducerede jeg NULL som SQLs markør for enhver form for manglende værdi. Jeg forklarede, at SQL ikke giver dig mulighed for at skelne mellem manglende og anvendelig (A-værdier) og manglende og uanvendelige (I-værdier) markører. Jeg forklarede også, hvordan sammenligninger, der involverer NULL'er, fungerer med konstanter, variabler, parametre og kolonner. I denne måned fortsætter jeg diskussionen ved at dække NULL behandlingsinkonsekvenser i forskellige T-SQL-elementer.

Jeg fortsætter med at bruge prøvedatabasen TSQLV5 som sidste måned i nogle af mine eksempler. Du kan finde scriptet, der opretter og udfylder denne database her, og dets ER-diagram her.

NULL behandlingsinkonsekvenser

Som du allerede har forstået, er NULL-behandling ikke triviel. Noget af forvirringen og kompleksiteten har at gøre med, at behandlingen af ​​NULL'er kan være inkonsekvent mellem forskellige elementer i T-SQL for lignende operationer. I de kommende afsnit beskriver jeg NULL-håndtering i lineære versus aggregerede beregninger, ON/WHERE/HAVING-klausuler, CHECK-begrænsning versus CHECK-indstilling, IF/WHILE/CASE-elementer, MERGE-sætningen, distinkt og gruppering, samt rækkefølge og unikhed.

Lineære kontra aggregerede beregninger

T-SQL, og det samme gælder for standard SQL, bruger forskellig NULL-håndteringslogik, når der anvendes en faktisk aggregeret funktion, såsom SUM, MIN og MAX på tværs af rækker, versus når der anvendes den samme beregning som en lineær på tværs af kolonner. For at demonstrere denne forskel vil jeg bruge to eksempeltabeller kaldet #T1 og #T2, som du opretter og udfylder ved at køre følgende kode:

DROP TABEL HVIS FINDER #T1, #T2; SELECT * INTO #T1 FROM ( VALUES(10, 5, NULL) ) AS D(col1, col2, col3); VÆLG * INTO #T2 FRA ( VALUES(10),(5),(NULL) ) AS D(col1);

Tabellen #T1 har tre kolonner kaldet col1, col2 og col3. Den har i øjeblikket én række med kolonneværdierne 10, 5 og NULL, henholdsvis:

VÆLG * FRA #T1;
col1 col2 col3------------ ----------- ----------10 5 NULL

Tabellen #T2 har en kolonne kaldet col1. Den har i øjeblikket tre rækker med værdierne 10, 5 og NULL i col1:

VÆLG * FRA #T2;
col1-----------105NULL

Når man anvender, hvad der i sidste ende er en aggregeret beregning, såsom addition som en lineær én på tværs af kolonner, giver tilstedeværelsen af ​​ethvert NULL-input et NULL-resultat. Følgende forespørgsel viser denne adfærd:

VÆLG col1 + col2 + col3 SOM totalFRA #T1;

Denne forespørgsel genererer følgende output:

i alt-----------NULL

Omvendt er faktiske aggregerede funktioner, som anvendes på tværs af rækker, designet til at ignorere NULL-input. Følgende forespørgsel viser denne adfærd ved hjælp af SUM-funktionen:

SELECT SUM(col1) AS totalFROM #T2;

Denne forespørgsel genererer følgende output:

total-----------15Advarsel:Nulværdi er elimineret af en samlet eller anden SET-operation.

Bemærk advarslen påbudt af SQL-standarden, der angiver tilstedeværelsen af ​​NULL-input, som blev ignoreret. Du kan undertrykke sådanne advarsler ved at deaktivere ANSI_WARNINGS-sessionsindstillingen.

På samme måde, når den anvendes på et inputudtryk, tæller COUNT-funktionen antallet af rækker med ikke-NULL-inputværdier (i modsætning til COUNT(*), som blot tæller antallet af rækker). Hvis du f.eks. erstatter SUM(col1) med COUNT(col1) i ovenstående forespørgsel, returneres antallet af 2.

Mærkeligt nok, hvis du anvender et COUNT-aggregat på en kolonne, der er defineret til ikke at tillade NULL'er, konverterer optimeringsværktøjet udtrykket COUNT() til COUNT(*). Dette muliggør brugen af ​​ethvert indeks med det formål at tælle i modsætning til at kræve brug af et indeks, der indeholder den pågældende kolonne. Det er endnu en grund ud over at sikre konsistensen og integriteten af ​​dine data, som burde opmuntre dig til at håndhæve begrænsninger som NOT NULL og andre. Sådanne begrænsninger giver optimeringsværktøjet mere fleksibilitet til at overveje mere optimale alternativer og undgå unødvendigt arbejde.

Baseret på denne logik dividerer AVG-funktionen summen af ​​ikke-NULL-værdier med antallet af ikke-NULL-værdier. Overvej følgende forespørgsel som et eksempel:

VÆLG AVG(1.0 * col1) AS avgallFRA #T2;

Her er summen af ​​de ikke-NULL col1 værdier 15 divideret med antallet af ikke-NULL værdier 2. Du multiplicerer col1 med den numeriske literal 1,0 for at tvinge implicit konvertering af heltal input værdier til numeriske ener for at få numerisk division og ikke heltal division. Denne forespørgsel genererer følgende output:

avgall---------7,500000

På samme måde ignorerer MIN- og MAX-aggregaterne NULL-input. Overvej følgende forespørgsel:

VÆLG MIN(col1) AS mincol1, MAX(col1) AS maxcol1FROM #T2;

Denne forespørgsel genererer følgende output:

mincol1 maxcol1------------ -----------5 10

At forsøge at anvende lineære beregninger, men at emulere aggregeret funktionssemantik (ignorer NULL'er), er ikke smukt. At emulere SUM, COUNT og AVG er ikke for kompliceret, men det kræver, at du tjekker hvert input for NULL, som sådan:

SELECT col1, col2, col3, CASE NÅR COALESCE(col1, col2, col3) ER NULL THEN NULL ELSE COALESCE(col1, 0) + COALESCE(col2, 0) + COALESCE(col3, 0) END AS sumall, CASE NÅR col1 IKKE ER NULL SÅ ER 1 ANDEN 0 END + TILFÆLDE NÅR col2 IKKE ER NULL SÅ ER 1 ANDEN 0 SLUT + SAG NÅR col3 IKKE ER NULL SÅ ER 1 ANDEN 0 END SOM cntall, TILFÆLDE NÅR COALESCE(col1, col2, col3) ER NULL. NULL ELSE 1.0 * (COALESCE(col1, 0) + COALESCE(col2, 0) + COALESCE(col3, 0)) / (CASE WHEN col1 IS NOT NULL THEN 1 ELSE 0 END + CASE WHEN col2 IS NOT NULL THEN 1 ELSE 0 END + CASE NÅR col3 IKKE ER NULL SÅ 1 ANDEN 0 END) END AS avgallFROM #T1;

Denne forespørgsel genererer følgende output:

col1 col2 col3 sumall cntall avgall------------------------------------------------ --- ----------- ---------------10 5 NULL 15 2 7,500000000000

At forsøge at anvende et minimum eller maksimum som en lineær beregning på mere end to inputkolonner er ret vanskeligt, selv før du tilføjer logikken til at ignorere NULLs, da det involverer indlejring af flere CASE-udtryk enten direkte eller indirekte (når du genbruger kolonnealiaser). For eksempel, her er en forespørgsel, der beregner maksimum blandt col1, col2 og col3 i #T1, uden den del, der ignorerer NULLs:

VÆLG col1, col2, col3, CASE NÅR col1 ER NULL ELLER col2 ER NULL ELLER col3 ER NULL SÅ NULL ELSE max2 END AS maxallFRA #T1 CROSS APPLY (VÆRDI(CASE NÅR col1>=col2 THEN col1 ELSE col2 END) SOM A1(maks1) KRYDS GÆLDER (VÆRDIER(CASE WHEN max1>=col3 THEN max1 ELSE col3 END)) AS A2(max2);

Denne forespørgsel genererer følgende output:

col1 col2 col3 maxall------------------------------------------------- -10 5 NULL NULL

Hvis du undersøger forespørgselsplanen, vil du finde følgende udvidede udtryk, der beregner det endelige resultat:

[Udtr1005] =Scalar Operator(CASE NÅR CASE NÅR [#T1].[col1] ER IKKE NULL SÅ [#T1].[col1] ELLES CASE WHEN [#T1].[col2] ER IKKE NULL SÅ [ #T1].[col2] ELSE [#T1].[col3] END END IS NULL THEN NULL ELSE CASE WHN CASE WHEN [#T1].[col1]>=[#T1].[col2] THEN [#T1] .[col1] ELLES [#T1].[col2] END>=[#T1].[col3] SÅ TILFÆLDE NÅR [#T1].[col1]>=[#T1].[col2] SÅ [#T1] .[col1] ELSE [#T1].[col2] END ELSE [#T1].[col3] END END)

Og det er, når der kun er tre kolonner involveret. Forestil dig at have et dusin kolonner involveret!

Tilføj nu logikken til at ignorere NULLs:

VÆLG col1, col2, col3, max2 AS maxallFROM #T1 CROSS APPLY (VÆRDIER(CASE WHEN col1>=col2 OR col2 IS NULL THEN col1 ELSE col2 END)) AS A1(max1) CROSS APPLY (VÆRDIER(CASE NÅR max1)>=col3 ELLER col3 ER NULL THEN max1 ELSE col3 END)) AS A2(max2);

Denne forespørgsel genererer følgende output:

col1 col2 col3 maxall------------------------------------------------- -10 5 NULL 10

Oracle har et par funktioner kaldet GREATEST og MINST, der anvender henholdsvis minimums- og maksimumsberegninger som lineære på inputværdierne. Disse funktioner returnerer en NULL givet ethvert NULL input, som de fleste lineære beregninger gør. Der var et åbent feedback-element, der bad om at få lignende funktioner i T-SQL, men denne anmodning blev ikke overført i deres seneste feedback-sideændring. Hvis Microsoft tilføjer sådanne funktioner til T-SQL, ville det være fantastisk at have en mulighed for at kontrollere, om NULL'er skal ignoreres eller ej.

I mellemtiden er der en meget mere elegant teknik sammenlignet med de førnævnte, der beregner enhver form for aggregat som en lineær på tværs af kolonner ved hjælp af faktiske aggregerede funktionssemantik, der ignorerer NULL'er. Du bruger en kombination af CROSS APPLY-operatoren og en afledt tabelforespørgsel mod en tabelværdikonstruktør, der roterer kolonner til rækker og anvender aggregatet som en faktisk aggregatfunktion. Her er et eksempel, der demonstrerer MIN- og MAX-beregningerne, men du kan bruge denne teknik med enhver aggregeret funktion, som du kan lide:

SELECT col1, col2, col3, maxall, minallFROM #T1 CROSS APPLY (SELECT MAX(mycol), MIN(mycol) FROM (VALUES(col1),(col2),(col3)) AS D1(mycol)) AS D2(maxall, minall);

Denne forespørgsel genererer følgende output:

col1 col2 col3 maxall minall------------------------------------------------- -----------10 5 NULL 10 5

Hvad hvis du vil det modsatte? Hvad hvis du har brug for at beregne et aggregat på tværs af rækker, men producere en NULL, hvis der er noget NULL-input? Antag for eksempel, at du skal summere alle col1-værdier fra #T1, men returner NULL, hvis nogen af ​​inputs er NULL. Dette kan opnås med følgende teknik:

SELECT SUM(col1) * NULLIF(MIN(CASE WHEN col1 IS NULL THEN 0 ELSE 1 END), 0) AS sumallFROM #T2;

Du anvender et MIN-aggregat til et CASE-udtryk, der returnerer nuller for NULL-input og ener for ikke-NULL-input. Hvis der er noget NULL-input, er resultatet af MIN-funktionen 0, ellers er det 1. Ved at bruge NULLIF-funktionen konverterer du et 0-resultat til et NULL. Du gange derefter resultatet af NULLIF-funktionen med den oprindelige sum. Hvis der er noget NULL-input, gange du den oprindelige sum med en NULL, hvilket giver en NULL. Hvis der ikke er NULL-input, multiplicerer du resultatet af den oprindelige sum med 1, hvilket giver den oprindelige sum.

Tilbage til lineære beregninger, der giver et NULL for ethvert NULL-input, gælder den samme logik for strengsammenkædning ved hjælp af +-operatoren, som følgende forespørgsel viser:

BRUG TSQLV5; SELECT empid, country, region, city, country + N',' + region + N',' + city AS ansættelseFRA HR.Employees;

Denne forespørgsel genererer følgende output:

empid country region by emplocation-------------------------------------------------------------- - -------------- ----------------1 USA WA Seattle USA,WA,Seattle2 USA WA Tacoma USA,WA,Tacoma3 USA WA Kirkland USA,WA,Kirkland4 USA WA Redmond USA,WA,Redmond5 UK NULL London NULL6 UK ​​NULL London NULL7 UK NULL London NULL8 USA WA Seattle USA,WA,Seattle9 UK NULL London NULL

Du vil sammenkæde medarbejdernes placeringsdele i én streng ved at bruge et komma som separator. Men du vil ignorere NULL-input. I stedet, når nogen af ​​inputs er en NULL, får du en NULL som resultat. Nogle deaktiverer CONCAT_NULL_YIELDS_NULL-sessionsindstillingen, som får et NULL-input til at blive konverteret til en tom streng til sammenkædningsformål, men denne mulighed anbefales ikke, da den anvender ikke-standard adfærd. Desuden vil du stå tilbage med flere på hinanden følgende separatorer, når der er NULL-input, hvilket typisk ikke er den ønskede adfærd. En anden mulighed er eksplicit at erstatte NULL-input med en tom streng ved hjælp af funktionerne ISNULL eller COALESCE, men dette resulterer normalt i en lang verbose kode. En meget mere elegant mulighed er at bruge CONCAT_WS-funktionen, som blev introduceret i SQL Server 2017. Denne funktion sammenkæder inputs, ignorerer NULLs, ved hjælp af separatoren, der er angivet som den første input. Her er løsningsforespørgslen ved hjælp af denne funktion:

SELECT empid, country, region, city, CONCAT_WS(N',', country, region, city) AS emlocationFROM HR.Employees;

Denne forespørgsel genererer følgende output:

empid country region by emplocation-------------------------------------------------------------- - -------------- ----------------1 USA WA Seattle USA,WA,Seattle2 USA WA Tacoma USA,WA,Tacoma3 USA WA Kirkland USA,WA,Kirkland4 USA WA Redmond USA,WA,Redmond5 UK NULL London UK,London6 UK NULL London UK,London7 UK NULL London UK,London8 USA WA Seattle USA,WA,Seattle9 UK NULL London UK,London

TÆNDT/HVOR/HAR

Når du bruger WHERE, HAVING og ON-forespørgselssætningerne til filtrering/matchningsformål, er det vigtigt at huske, at de bruger prædikatlogik med tre værdier. Når du har tre-værdi-logik involveret, vil du præcist identificere, hvordan klausulen håndterer SAND, FALSK og UKENDTE tilfælde. Disse tre klausuler er designet til at acceptere TRUE tilfælde og afvise FALSE og UKENDTE tilfælde.

For at demonstrere denne adfærd vil jeg bruge en tabel kaldet Kontakter, som du opretter og udfylder ved at køre følgende kode:.

DROP TABEL HVIS FINDER dbo.Contacts;GO OPRET TABEL dbo.Contacts( id INT IKKE NULL BEGRÆNSNING PK_Contacts PRIMÆR NØGLE, navn VARCHAR(10) IKKE NULL, timepris NUMERIC(12, 2) NULL BEGRÆNSNING KONSTRAINT. )); INSERT INTO dbo.Contacts(id, navn, timepris) VALUES (1, 'A', 100.00),(2, 'B', 200.00),(3, 'C', NULL);

Bemærk, at kontakt 1 og 2 har gældende timepriser, og kontakt 3 har ikke, så dens timepris er sat til NULL. Overvej følgende forespørgsel på udkig efter kontakter med en positiv timepris:

VÆLG id, navn, timeprisFRA dbo.ContactsWHERE timepris> 0,00;

Dette prædikat evalueres til TRUE for kontakt 1 og 2, og til UNKNOWN for kontakt 3, derfor indeholder output kun kontakt 1 og 2:

id navn timepris----------- ---------- ----------1 A 100.002 B 200.00

Tanken her er, at når du er sikker på, at prædikatet er sandt, vil du returnere rækken, ellers vil du kassere den. Dette kan umiddelbart virke trivielt, indtil du indser, at nogle sprogelementer, der også bruger prædikater, fungerer anderledes.

CHECK-begrænsning versus CHECK-indstilling

En CHECK-begrænsning er et værktøj, som du bruger til at håndhæve integritet i en tabel baseret på et prædikat. Prædikatet evalueres, når du forsøger at indsætte eller opdatere rækker i tabellen. I modsætning til forespørgselsfiltrering og matchende klausuler, der accepterer TRUE-tilfælde og afviser FALSE og UNKNOWN-tilfælde, er en CHECK-begrænsning designet til at acceptere TRUE og UNKNOWN-tilfælde og afvise FALSE-tilfælde. Tanken her er, at når du er sikker på, at prædikatet er falsk, vil du afvise forsøget på ændring, ellers vil du tillade det.

Hvis du undersøger definitionen af ​​vores kontakttabel, vil du bemærke, at den har følgende CHECK-begrænsning, hvilket afviser kontakter med ikke-positive timepriser:

CONSTRAINT CHK_Contacts_hourlyrate CHECK(hourlyrate> 0,00)

Bemærk, at begrænsningen bruger det samme prædikat som det, du brugte i det forrige forespørgselsfilter.

Prøv at tilføje en kontaktperson med en positiv timepris:

INSERT INTO dbo.Contacts(id, name, hourlyrate) VALUES (4, 'D', 150.00);

Dette forsøg lykkes.

Prøv at tilføje en kontaktperson med en timepris på NULL:

INSERT INTO dbo.Contacts(id, name, hourlyrate) VALUES (5, 'E', NULL);

Dette forsøg lykkes også, da en CHECK-begrænsning er designet til at acceptere TRUE og UKENDTE tilfælde. Det er tilfældet, hvor et forespørgselsfilter og en CHECK-begrænsning er designet til at fungere anderledes.

Prøv at tilføje en kontakt med en ikke-positiv timepris:

INSERT INTO dbo.Contacts(id, name, hourlyrate) VALUES (6, 'F', -100.00);

Dette forsøg mislykkes med følgende fejl:

Msg 547, Level 16, State 0, Line 454
INSERT-sætningen var i konflikt med CHECK-begrænsningen "CHK_Contacts_hourlyrate". Konflikten opstod i databasen "TSQLV5", tabel "dbo.Contacts", kolonnen "timepris".

T-SQL giver dig også mulighed for at håndhæve integriteten af ​​ændringer gennem visninger ved hjælp af en CHECK-indstilling. Nogle mener, at denne mulighed tjener et lignende formål som en CHECK-begrænsning, så længe du anvender ændringen gennem visningen. Overvej f.eks. følgende visning, som bruger et filter baseret på prædikatet timepris> 0,00 og er defineret med CHECK-indstillingen:

OPRET ELLER ÆNDRING AF VISNING dbo.MyContactsASSELECT id, navn, timeprisFRA dbo.ContactsWHERE timepris> 0,00WITH CHECK Option;

Som det viser sig, i modsætning til en CHECK-begrænsning, er view CHECK-indstillingen designet til at acceptere TRUE tilfælde og afvise både FALSE og UKENDTE tilfælde. Så det er faktisk designet til at opføre sig mere som forespørgselsfilteret normalt gør, også med det formål at håndhæve integritet.

Prøv at indsætte en række med en positiv timepris gennem visningen:

INSERT INTO dbo.MyContacts(id, name, hourlyrate) VALUES (7, 'G', 300.00);

Dette forsøg lykkes.

Prøv at indsætte en række med en NULL timepris gennem visningen:

INSERT INTO dbo.MyContacts(id, name, hourlyrate) VALUES (8, 'H', NULL);

Dette forsøg mislykkes med følgende fejl:

Msg 550, Level 16, State 1, Line 473
Indsættelsesforsøget eller opdateringen mislykkedes, fordi målvisningen enten angiver WITH CHECK OPTION eller spænder over en visning, der specificerer WITH CHECK OPTION, og en eller flere rækker, der stammer fra operationen, ikke kvalificere sig under begrænsningen CHECK OPTION.

Tanken her er, at når du har tilføjet CHECK-indstillingen til visningen, vil du kun tillade ændringer, der resulterer i rækker, der returneres af visningen. Det er lidt anderledes end tænkningen med en CHECK-begrænsning - afvis ændringer, for hvilke du er sikker på, at prædikatet er falsk. Dette kan være lidt forvirrende. Hvis du vil have visningen til at tillade ændringer, der sætter timeprisen til NULL, skal du bruge forespørgselsfilteret til også at tillade dem ved at tilføje ELLER timeprisen ER NULL. Du skal bare indse, at en CHECK-begrænsning og en CHECK-indstilling er designet til at fungere anderledes i forhold til UNKENDTE tilfælde. Førstnævnte accepterer det, mens sidstnævnte afviser det.

Forespørg i tabellen Kontaktpersoner efter alle ovenstående ændringer:

VÆLG id, navn, timeprisFRA dbo.Contacts;

Du bør få følgende output på dette tidspunkt:

id navn timepris----------- ---------- ----------1 A 100.002 B 200.003 C NULL4 D 150.005 E NULL7 G 300,00

HVIS/MENS/CASE

Sprogelementerne IF, WHILE og CASE arbejder med prædikater.

IF-sætningen er designet som følger:

HVIS ELSE

Det er intuitivt at forvente at have en TRUE-blok efter IF-sætningen og en FALSE-blok efter ELSE-sætningen, men du skal indse, at ELSE-sætningen faktisk bliver aktiveret, når prædikatet er FALSE eller UKENDT. Teoretisk set kunne et logisk sprog med tre værdier have haft en IF-sætning med en adskillelse af de tre tilfælde. Noget som dette:

HVIS  NÅR SAND  NÅR FALSE  NÅR UKENDT 

Og tillad endda kombinationer af logiske udfald, så hvis du ville kombinere FALSK og UKENDT i én sektion, kunne du bruge noget som dette:

HVIS  NÅR SAND  HVIS FALSK ELLER UKENDT 

I mellemtiden kan du efterligne sådanne konstruktioner ved at indlejre IF-ELSE-sætninger og eksplicit kigge efter NULL'er i operanderne med IS NULL-operatoren.

WHILE-sætningen har kun en TRUE-blok. Den er designet som følger:

WHILE  

Udsagnet eller BEGIN-END-blokken, der danner løkkens krop, aktiveres, mens prædikatet er TURE. Så snart prædikatet er FALSE eller UNKNOWN, overføres kontrollen til sætningen efter WHILE-løkken.

I modsætning til IF og WHILE, som er sætninger, der udfører kode, er CASE et udtryk, der returnerer en værdi. Syntaksen for en søgt CASE-udtryk er som følger:

CASE WHEN NÅR ... NÅR ELSE END

Et CASE-udtryk er designet til at returnere udtrykket efter THEN-udtrykket, der svarer til det første WHEN-prædikat, der evalueres til TRUE. Hvis der er en ELSE-sætning, aktiveres den, hvis intet WHEN-prædikat er SAND (alle er FALSK eller UKENDT). Uden en eksplicit ELSE-klausul bruges en implicit ELSE NULL. Hvis du vil håndtere en UKENDT sag separat, kan du eksplicit søge efter NULL'er i prædikatets operander ved hjælp af IS NULL-operatoren.

En simpel CASE-udtryk bruger implicitte lighedsbaserede sammenligninger mellem kildeudtrykket og de sammenlignede udtryk:

CASE NÅR NÅR ... NÅR ELSE END

Det simple CASE-udtryk er designet på samme måde som det søgte CASE-udtryk i forhold til håndteringen af ​​den tre-værdi-logik, men da sammenligningerne bruger en implicit lighedsbaseret sammenligning, kan du ikke håndtere UNKNOWN casen separat. Et forsøg på at bruge en NULL i et af de sammenlignede udtryk i WHEN-sætningerne er meningsløst, da sammenligningen ikke vil resultere i TRUE, selv når kildeudtrykket er NULL. Overvej følgende eksempel:

DECLARE @input AS INT =NULL; SELECT CASE @input WHEN NULL THEN 'Input is NULL' ELSE 'Input is not NULL' END;

Dette konverteres implicit til følgende:

DECLARE @input AS INT =NULL; VÆLG CASE, NÅR @input =NULL SÅ 'Input er NULL' ELSE 'Input er ikke NULL' END;

Følgelig er resultatet:

Input er ikke NULL

For at detektere et NULL-input skal du bruge den søgte CASE-udtrykssyntaks og IS NULL-operatoren, som sådan:

DECLARE @input AS INT =NULL; VÆLG CASE, NÅR @input ER NULL SÅ 'Input er NULL' ELSE 'Input er ikke NULL' END;

Denne gang er resultatet:

Input er NULL

SAMLET

MERGE-sætningen bruges til at flette data fra en kilde til et mål. Du bruger et fletteprædikat til at identificere følgende tilfælde og anvende en handling mod målet:

  • En kilderække matches af en målrække (aktiveres, når der findes et match for kilderækken, hvor fletteprædikatet er SAND):anvend OPDATERING eller SLET mod målet
  • En kilderække matches ikke af en målrække (aktiveret, når der ikke findes nogen overensstemmelser for kilderækken, hvor fletteprædikatet er SAND, snarere for alt prædikatet er FALSK eller UKENDT):anvend en INSERT mod målet
  • En målrække matches ikke af en kilderække (aktiveret, når der ikke findes overensstemmelser for målrækken, hvor fletteprædikatet er SAND, snarere for alt prædikatet er FALSK eller UKENDT):anvend OPDATERING eller SLET mod målet

Alle tre scenarier adskiller TRUE til én gruppe og FALSK eller UKENDT til en anden. Du får ikke separate sektioner til håndtering af SAND, håndtering af FALSK og håndtering af UKENDTE sager.

For at demonstrere dette bruger jeg en tabel kaldet T3, som du opretter og udfylder ved at køre følgende kode:

DROP TABLE IF EXISTS dbo.T3;GO OPRET TABEL dbo.T3(col1 INT NULL, col2 INT NULL, CONSTRAINT UNQ_T3 UNIQUE(col1)); INSERT INTO dbo.T3(col1) VALUES(1),(2),(NULL);

Overvej følgende MERGE-sætning:

FLET TIL dbo.T3 SOM TGTUSING (VÆRDIER(1, 100), (3, 300)) SOM SRC(col1, col2) PÅ SRC.col1 =TGT.col1NÅR MATCHED SÅ OPDATERES INDSTIL TGT.col2 =SRC.col2HVORNÅR IKKE MATCHED, SÅ INDSÆT(col1, col2) VALUES(SRC.col1, SRC.col2)NÅR DE IKKE MATCHES EFTER KILDE, SÅ OPDATERE SET col2 =-1; VÆLG col1, col2 FRA dbo.T3;

Kilderækken, hvor col1 er 1, matches af målrækken, hvor col1 er 1 (prædikatet er TRUE), og derfor er målrækkens col2 sat til 100.

Kilderækken, hvor col1 er 3, matches ikke af nogen målrække (for alle er prædikatet FALSK eller UKENDT), og derfor indsættes en ny række i T3 med 3 som col1-værdien og 300 som col2-værdien.

Målrækkerne, hvor col1 er 2, og hvor col1 er NULL, matches ikke af nogen kilderække (for alle rækker er prædikatet FALSK eller UKENDT), og derfor er col2 i målrækkerne i begge tilfælde sat til -1.

Forespørgslen mod T3 returnerer følgende output efter at have udført ovenstående MERGE-sætning:

col1 col2----------- -----------1 1002 -1NULL -13 300

Hold bord T3 omkring; det bruges senere.

Særlighed og gruppering

I modsætning til sammenligninger, der udføres ved hjælp af ligheds- og ulighedsoperatorer, grupperer sammenligninger, der udføres til distinctness og grupperingsformål, NULLs sammen. En NULL anses for ikke at være adskilt fra en anden NULL, men en NULL anses for at være adskilt fra en ikke-NULL værdi. Følgelig fjerner anvendelse af et DISTINCT-udtryk duplikerede forekomster af NULL'er. Følgende forespørgsel viser dette:

VÆLG DISTINKT land, region FRA HR.Employees;

Denne forespørgsel genererer følgende output:

landsregion--------------- ---------------UK NULLUSA WA

Der er flere ansatte med landet USA og regionen NULL, og efter fjernelse af dubletter viser resultatet kun én forekomst af kombinationen.

Ligesom distinctness grupperer gruppering også NULL'er sammen, som følgende forespørgsel viser:

VÆLG land, region, COUNT(*) SOM numemps FRA HR.Medarbejdere GRUPPE EFTER land, region;

Denne forespørgsel genererer følgende output:

country region numemps--------------- --------------------- -----------UK NULL 4USA WA 5

Igen blev alle fire medarbejdere med landet UK og region NULL grupperet sammen.

Bestilling

Bestilling behandler flere NULL'er som at have den samme bestillingsværdi. SQL-standarden overlader det til implementeringen at vælge, om der skal bestilles NULL først eller sidst sammenlignet med ikke-NULL værdier. Microsoft valgte at betragte NULL'er som havende lavere rækkefølgeværdier sammenlignet med ikke-NULL'er i SQL Server, så når du bruger stigende rækkefølge, bestiller T-SQL NULL'er først. Følgende forespørgsel viser dette:

VÆLG id, navn, timeprisFRA dbo.Kontaktpersoner BESTIL EFTER timepris;

Denne forespørgsel genererer følgende output:

id navn timepris----------- ---------- -----------3 C NULL5 E NULL1 A 100.004 D 150.002 B 200.007 G 300,00

Næste måned vil jeg tilføje mere om dette emne og diskutere standardelementer, der giver dig kontrol over NULL-ordreadfærd og løsningerne for disse elementer i T-SQL.

Unik

Når man håndhæver unikhed på en NULL-bar kolonne ved hjælp af enten en UNIQUE begrænsning eller et unikt indeks, behandler T-SQL NULL-værdier ligesom ikke-NULL-værdier. Den afviser duplikerede NULL'er, som om en NULL ikke er unik fra en anden NULL.

Husk på, at vores tabel T3 har en UNIK begrænsning defineret på col1. Her er dens definition:

BEGRENSNING UNQ_T3 UNIQUE(col1)

Forespørg T3 for at se dets aktuelle indhold:

VÆLG * FRA dbo.T3;

Hvis du kørte alle ændringerne mod T3 fra de tidligere eksempler i denne artikel, skulle du få følgende output:

col1 col2----------- -----------1 1002 -1NULL -13 300

Forsøg at tilføje en anden række med en NULL i col1:

INSERT INTO dbo.T3(col1, col2) VALUES(NULL, 400);

Du får følgende fejlmeddelelse:

Msg 2627, Level 14, State 1, Line 558
Overtrædelse af UNIQUE KEY constraint 'UNQ_T3'. Kan ikke indsætte dubletnøgle i objektet 'dbo.T3'. Dubletnøgleværdien er ().

Denne adfærd er faktisk ikke-standard. Næste måned vil jeg beskrive standardspecifikationen, og hvordan man emulerer den i T-SQL.

Konklusion

I denne anden del af serien om NULL-kompleksiteter fokuserede jeg på NULL-behandlingsinkonsekvenser mellem forskellige T-SQL-elementer. Jeg dækkede lineære versus aggregerede beregninger, filtrering og matchende klausuler, CHECK-begrænsningen versus CHECK-indstillingen, IF, WHILE og CASE-elementer, MERGE-sætningen, distinkthed og gruppering, rækkefølge og unikhed. De inkonsekvenser, jeg dækkede, understreger yderligere, hvor vigtigt det er at forstå behandlingen af ​​NULL'er korrekt på den platform, du bruger, for at sikre, at du skriver korrekt og robust kode. Næste måned fortsætter jeg serien ved at dække SQL-standard NULL-behandlingsmulighederne, der ikke er tilgængelige i T-SQL, og giver løsninger, der understøttes i T-SQL.


  1. Sådan opretter du et databasediagram i Access

  2. Sådan vælger du en brugervenlig database til din virksomhed

  3. Sådan vises alle visninger i en PostgreSQL-database

  4. Parser til Oracle SQL