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

Grundlæggende om tabeludtryk, del 11 - Synspunkter, ændringsovervejelser

Denne artikel er den ellevte del i en serie om tabeludtryk. Indtil videre har jeg dækket afledte tabeller og CTE'er og for nylig startet dækningen af ​​visninger. I del 9 sammenlignede jeg visninger med afledte tabeller og CTE'er, og i del 10 diskuterede jeg DDL-ændringer og implikationerne af at bruge SELECT * i visningens indre forespørgsel. I denne artikel fokuserer jeg på ændringsovervejelser.

Som du sikkert ved, har du lov til at ændre data i basistabeller indirekte gennem navngivne tabeludtryk som visninger. Du kan styre ændringstilladelser mod visninger. Faktisk kan du give brugere tilladelse til at ændre data gennem visninger uden at give dem tilladelse til at ændre de underliggende tabeller direkte.

Du skal være opmærksom på visse kompleksiteter og begrænsninger, der gælder for ændringer gennem visninger. Interessant nok kan nogle af de understøttede ændringer ende med overraskende resultater, især hvis brugeren, der ændrer dataene, ikke er klar over, at de interagerer med en visning. Du kan pålægge yderligere begrænsninger for ændringer gennem visninger ved at bruge en mulighed kaldet CHECK OPTION, som jeg vil dække i denne artikel. Som en del af dækningen vil jeg beskrive en mærkelig uoverensstemmelse mellem, hvordan CHECK OPTION i en visning og en CHECK-begrænsning i en tabel håndterer ændringer – specifikt dem, der involverer NULL.

Eksempel på data

Som eksempeldata til denne artikel vil jeg bruge tabeller kaldet Orders and OrderDetails. Brug følgende kode til at oprette disse tabeller i tempdb og udfylde dem med nogle indledende eksempeldata:

BRUG tempdb;GO DROP TABLE IF EXISTS dbo.OrderDetails, dbo.Orders;GO CREATE TABLE dbo.Orders( orderid INT NOT NULL CONSTRAINT PK_Orders PRIMARY KEY, orderdate DATE NOT NULL, shippeddate DATE NULL); INSERT INTO dbo.Orders(orderid, orderdate, shippeddate) VALUES(1, '20210802', '20210804'), (2, '20210802', '20210805'), (3, '20210804', '202104', '202104'), (8 4, '20210826', NULL), (5, '20210827', NULL); CREATE TABLE dbo.OrderDetails( ordre-id INT IKKE NULL BEGRÆNSNING FK_OrderDetails_Orders REFERENCER dbo.Orders, produkt-id INT IKKE NULL, antal INT IKKE NULL, enhedspris NUMERIC(12, 2) IKKE NULL, rabat NUMERIC(5, PKMA) CONSTRAINT NOT(5, PKMA) KEY(orderid, productid)); INSERT INTO dbo.OrderDetails(orderid, productid, quty, unitprice, rabat) VALUES(1, 1001, 5, 10,50, 0,05), (1, 1004, 2, 20,00, 0,00), (2, 1003, 59, 1, 0,10); , (4, 1005, 1, 30,10, 0,05), (5, 1003, 5, 54,99, 0,00), (5, 1006, 2, 12,30, 0,08);

Tabellen Ordrer indeholder ordreoverskrifter, og tabellen Ordredetaljer indeholder ordrelinjer. Ikke-afsendte ordrer har en NULL i kolonnen afsendt dato. Hvis du foretrækker et design, der ikke bruger NULL'er, kan du bruge en specifik fremtidig dato for ikke-afsendte ordrer, såsom "99991231."

TJEK MULIGHED

For at forstå de omstændigheder, hvor du ønsker at bruge KONTROLMULIGHEDEN som en del af en visnings definition, vil vi først undersøge, hvad der kan ske, når du ikke bruger det.

Følgende kode opretter en visning kaldet FastOrders, der repræsenterer ordrer afsendt inden for syv dage, siden de blev afgivet:

OPRET ELLER ÆNDRING AF VISNING dbo.FastOrdersAS SELECT orderid, orderdate, shippeddate FRA dbo.Orders WHERE DATEDIFF(dag, ordredato, afsendelsesdato) <=7;GO

Brug følgende kode til at indsætte en ordre, der er afsendt to dage efter afgivelse, gennem visningen:

INSERT INTO dbo.FastOrders(orderid, orderdate, shippeddate) VALUES(6, '20210805', '20210807');

Spørg visningen:

VÆLG * FRA dbo.FastOrders;

Du får følgende output, som inkluderer den nye ordre:

orderid orderdate shippeddate ------------ ---------- ----------1 2021-08-02 2021-08-042 2021 -08-02 2021-08-053 2021-08-04 2021-08-066 2021-08-05 2021-08-07

Forespørg den underliggende tabel:

VÆLG * FRA dbo.Orders;

Du får følgende output, som inkluderer den nye ordre:

orderid orderdate shippeddate ------------ ---------- ----------1 2021-08-02 2021-08-042 2021 -08-02 2021-08-053 2021-08-04 2021-08-064 2021-08-26 NULL5 2021-08-27 NULL6 2021-08-05 2021-08-07

Rækken blev indsat i den underliggende basistabel gennem visningen.

Indsæt derefter gennem visningen en række, der er afsendt 10 dage efter at være blevet placeret, hvilket modsiger visningens indre forespørgselsfilter:

INSERT INTO dbo.FastOrders(orderid, orderdate, shippeddate) VALUES(7, '20210805', '20210815');

Opgørelsen fuldføres med succes og rapporterer en række berørt.

Spørg visningen:

VÆLG * FRA dbo.FastOrders;

Du får følgende output, som udelukker den nye ordre:

orderid orderdate shippeddate ------------ ---------- ----------1 2021-08-02 2021-08-042 2021 -08-02 2021-08-053 2021-08-04 2021-08-066 2021-08-05 2021-08-07

Hvis du ved, at FastOrders er en visning, kan alt dette virke fornuftigt. Rækken blev trods alt indsat i den underliggende tabel, og den opfylder ikke visningens indre forespørgselsfilter. Men hvis du ikke er klar over, at FastOrders er en visning og ikke en basistabel, ville denne adfærd virke overraskende.

Forespørg på den underliggende ordretabel:

VÆLG * FRA dbo.Orders;

Du får følgende output, som inkluderer den nye ordre:

orderid orderdate shippeddate ------------ ---------- ----------1 2021-08-02 2021-08-042 2021 -08-02 2021-08-053 2021-08-04 2021-08-064 2021-08-26 NULL5 2021-08-27 NULL6 2021-08-05 2021-08-057 02020-2020 15

Du kan opleve en lignende overraskende adfærd, hvis du gennem visningen opdaterer shippeddate-værdien i en række, der i øjeblikket er en del af visningen, til en dato, der gør, at den ikke længere kvalificerer sig som en del af visningen. En sådan opdatering er normalt tilladt, men igen, den finder sted i den underliggende basistabel. Hvis du forespørger på visningen efter en sådan opdatering, ser den ændrede række ud til at være væk. I praksis er det stadig der i den underliggende tabel, det betragtes bare ikke som en del af visningen længere.

Kør følgende kode for at slette de rækker, du tilføjede tidligere:

DELETE FROM dbo.Orders WHERE orderid>=6;

Hvis du vil forhindre ændringer, der er i konflikt med visningens indre forespørgselsfilter, skal du tilføje MED CHECK OPTION i slutningen af ​​den indre forespørgsel som en del af visningsdefinitionen, som sådan:

OPRET ELLER ÆNDRING AF VISNING dbo.FastOrdersAS SELECT orderid, orderdate, shippeddate FRA dbo.Orders WHERE DATEDIFF(dag, ordredato, afsendedato) <=7 MED KONTROLMULIGHED;GO

Indsættelser og opdateringer gennem visningen er tilladt, så længe de overholder den indre forespørgsels filter. Ellers bliver de afvist.

Brug f.eks. følgende kode til at indsætte en række gennem visningen, der ikke er i konflikt med det indre forespørgselsfilter:

INSERT INTO dbo.FastOrders(orderid, orderdate, shippeddate) VALUES(6, '20210805', '20210807');

Rækken er tilføjet.

Forsøg på at indsætte en række, der ikke er i konflikt med filteret:

INSERT INTO dbo.FastOrders(orderid, orderdate, shippeddate) VALUES(7, '20210805', '20210815');

Denne gang afvises rækken med følgende fejl:

Niveau 16, tilstand 1, linje 135
Indsættelsesforsøget eller opdateringen mislykkedes, fordi målvisningen enten specificerer WITH CHECK OPTION eller spænder over en visning, der specificerer WITH CHECK OPTION, og en eller flere rækker, der stammer fra operationen, kvalificerede sig ikke under CHECK OPTION begrænsning.

NULL-uoverensstemmelser

Hvis du har arbejdet med T-SQL i nogen tid, er du sandsynligvis udmærket klar over de førnævnte modifikationskompleksiteter, og funktionen CHECK OPTION tjener. Ofte finder selv erfarne mennesker NULL-håndteringen af ​​CHECK OPTION som overraskende. I årevis plejede jeg at tænke på CHECK OPTION i en visning som at tjene den samme funktion som en CHECK-begrænsning i en basistabels definition. Det er også sådan, jeg plejede at beskrive denne mulighed, når jeg skrev eller underviste om det. Så længe der ikke er NULL'er involveret i filterprædikatet, er det faktisk praktisk at tænke på de to i lignende termer. De opfører sig konsekvent i sådan et tilfælde - de accepterer rækker, der stemmer overens med prædikatet, og afviser dem, der er i konflikt med det. De to håndterer dog NULLs inkonsekvent.

Når du bruger KONTROLMULIGHEDEN, tillades en ændring gennem visningen, så længe prædikatet evalueres til sandt, ellers afvises det. Dette betyder, at det afvises, når visningens prædikat evalueres til falsk eller ukendt (når en NULL er involveret). Med en CHECK-begrænsning er ændringen tilladt, når begrænsningens prædikat evalueres til sand eller ukendt, og afvist, når prædikatet evalueres til falsk. Det er en interessant forskel! Lad os først se dette i aktion, så prøver vi at finde ud af logikken bag denne inkonsekvens.

Forsøg på at indsætte en række gennem visningen med en NULL afsendelsesdato:

INSERT INTO dbo.FastOrders(orderid, orderdate, shippeddate) VALUES(8, '20210828', NULL);

Visningens prædikat evalueres til ukendt, og rækken afvises med følgende fejl:

Msg 550, Level 16, State 1, Line 147
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.

Lad os prøve en lignende indsættelse mod en basistabel med en CHECK-begrænsning. Brug følgende kode til at tilføje en sådan begrænsning til vores ordres tabeldefinition:

ÆNDRINGSTABEL dbo.Ordre ADD CONSTRAINT CHK_Orders_FastOrder CHECK(DATEDIFF(dag, ordredato, afsendelsesdato) <=7);

For det første, for at sikre, at begrænsningen fungerer, når der ikke er nogen NULL involverede, prøv at indsætte følgende ordre med en afsendelsesdato 10 dage væk fra ordredatoen:

INSERT INTO dbo.Orders(orderid, orderdate, shippeddate) VALUES(7, '20210805', '20210815');

Dette forsøg på indsættelse afvises med følgende fejl:

Msg 547, Level 16, State 0, Line 159
INSERT-sætningen var i konflikt med CHECK-begrænsningen "CHK_Orders_FastOrder". Konflikten opstod i databasen "tempdb", tabellen "dbo.Orders".

Brug følgende kode til at indsætte en række med en NULL afsendelsesdato:

INSERT INTO dbo.Orders(orderid, orderdate, shippeddate) VALUES(8, '20210828', NULL);

En CHECK-begrænsning formodes at afvise falske tilfælde, men i vores tilfælde evalueres prædikatet til ukendt, så rækken tilføjes med succes.

Forespørg i ordretabellen:

VÆLG * FRA dbo.Orders;

Du kan se den nye rækkefølge i outputtet:

orderid orderdate shippeddate ------------ ---------- ----------1 2021-08-02 2021-08-042 2021 -08-02 2021-08-053 2021-08-04 2021-08-064 2021-08-26 NULL5 2021-08-27 NULL6 2021-08-05 2021-08-0178 8-2pre 8-22> 

Hvad er logikken bag denne inkonsekvens? Du kan argumentere for, at en CHECK-begrænsning kun bør håndhæves, når begrænsningens prædikat tydeligt er overtrådt, hvilket betyder, når det vurderes til at være falsk. På denne måde, hvis du vælger at tillade NULL'er i den pågældende kolonne, tillades rækker med NULL'er i kolonnen, selvom begrænsningens prædikat evalueres til ukendt. I vores tilfælde repræsenterer vi ikke-afsendte ordrer med et NULL i kolonnen afsendt dato, og vi tillader ikke-afsendte ordrer i tabellen, mens vi kun håndhæver reglen om "hurtige ordrer" for afsendte ordrer.

Argumentet for at bruge en anden logik med en visning er, at en ændring kun bør tillades gennem visningen, hvis resultatrækken er en gyldig del af visningen. Hvis visningens prædikat evalueres til ukendt, f.eks. når afsendelsesdatoen er NULL, er resultatrækken ikke en gyldig del af visningen, og derfor afvises den. Kun rækker, for hvilke prædikatet vurderes til at være sandt, er en gyldig del af visningen og er derfor tilladt.

NULL tilføjer meget kompleksitet til sproget. Kan du lide dem eller ej, hvis dine data understøtter dem, vil du være sikker på, at du forstår, hvordan T-SQL håndterer dem.

På dette tidspunkt kan du droppe CHECK-begrænsningen fra ordretabellen og også droppe FastOrders-visningen til oprydning:

ALTER TABLE dbo.Orders DROP CONSTRAINT CHK_Orders_FastOrder;DROP VISNING HVIS FINDER dbo.FastOrders;

TOP/OFFSET-FETCH-begrænsning

Ændringer gennem visninger, der involverer TOP- og OFFSET-FETCH-filtrene, er normalt tilladt. Men ligesom med vores tidligere diskussion om synspunkter defineret uden KONTROLMULIGHED, kan resultatet af en sådan ændring virke mærkeligt for brugeren, hvis de ikke er klar over, at de interagerer med en visning.

Overvej følgende visning, der repræsenterer de seneste ordrer som et eksempel:

OPRET ELLER ÆNDRING AF VISNING dbo.RecentOrdersAS SELECT TOP (5) orderid, orderdate, shippeddate FRA dbo.Orders BESTILLING BY orderdate DESC, orderid DESC;GO

Brug følgende kode til at indsætte seks ordrer gennem visningen Seneste Ordrer:

INSERT INTO dbo.RecentOrders(orderid, orderdate, shippeddate) VALUES(9, '20210801', '20210803'), (10, '20210802', '20210804'), (11, '202108029', '08321029', '083210803' ), (12, '20210830', '20210902'), (13, '20210830', '20210903'), (14, '20210831', '20210903');

Spørg visningen:

VÆLG * FRA dbo.RecentOrders;

Du får følgende output:

orderid orderdate shippeddate----------- ---------- -----------14 2021-08-31 2021-09-0313 2021 -08-30 2021-09-0312 2021-08-30 2021-09-0211 2021-08-29 2021-08-318 2021-08-28 NULL

Af de seks indsatte ordrer er kun fire en del af visningen. Dette virker helt fornuftigt, hvis du er klar over, at du forespørger på en visning, der er baseret på en forespørgsel med et TOP-filter. Men det kan virke mærkeligt, hvis du tænker, at du forespørger på en basistabel.

Forespørg direkte på den underliggende ordretabel:

VÆLG * FRA dbo.Orders;

Du får følgende output, der viser alle tilføjede ordrer:

orderid orderdate shippeddate ------------ ---------- ----------1 2021-08-02 2021-08-042 2021 -08-02 2021-08-053 2021-08-04 2021-08-064 2021-08-26 NULL5 2021-08-27 NULL6 2021-08-05 2021-08-078 8-2028 20208 2020 -01 2021-08-0310 2021-08-02 2021-08-0411 2021-08-29 2021-08-3112 2021-08-30 2021-09-0213 2001-9-021-2001-2021-2001-202-1 -31 2021-09-03

Hvis du føjer CHECK OPTION til visningsdefinitionen, vil INSERT og UPDATE-sætninger mod visningen blive afvist. Brug følgende kode til at anvende denne ændring:

OPRET ELLER ÆNDRING AF VISNING dbo.RecentOrdersAS SELECT TOP (5) orderid, orderdate, shippeddate FRA dbo.Orders BESTILLING BY orderdate DESC, orderid DESC WITH CHECK Option;GO

Prøv at tilføje en ordre via visningen:

INSERT INTO dbo.RecentOrders(orderid, orderdate, shippeddate) VALUES(15, '20210801', '20210805');

Du får følgende fejlmeddelelse:

Msg 4427, Level 16, State 1, Line 247
Kan ikke opdatere visningen "dbo.RecentOrders", fordi den eller en visning, den refererer til, blev oprettet med WITH CHECK OPTION, og dens definition indeholder en TOP- eller OFFSET-klausul.

SQL Server forsøger ikke at være for smart her. Det vil afvise ændringen, selvom den række, du forsøger at indsætte, ville blive en gyldig del af visningen på det tidspunkt. Prøv f.eks. at tilføje en ordre med en nyere dato, som ville falde i top 5 på dette tidspunkt:

INSERT INTO dbo.RecentOrders(orderid, orderdate, shippeddate) VALUES(15, '20210904', '20210906');

Det forsøgte at indsætte er stadig afvist med følgende fejl:

Msg 4427, Level 16, State 1, Line 254
Kan ikke opdatere visningen "dbo.RecentOrders", fordi den eller en visning, den refererer til, blev oprettet med WITH CHECK OPTION, og dens definition indeholder en TOP- eller OFFSET-klausul.

Prøv at opdatere en række gennem visningen:

OPDATERING dbo.RecentOrders SET shippeddate =DATEADD(dag, 2, ordredato);

I dette tilfælde afvises ændringsforsøget også med følgende fejl:

Msg 4427, Level 16, State 1, Line 260
Kan ikke opdatere visningen "dbo.RecentOrders", fordi den eller en visning, den refererer til, blev oprettet med WITH CHECK OPTION, og dens definition indeholder en TOP- eller OFFSET-klausul.

Vær opmærksom på, at definering af en visning baseret på en forespørgsel med TOP eller OFFSET-FETCH og CHECK OPTION vil resultere i manglende understøttelse af INSERT- og UPDATE-sætninger gennem visningen.

Sletninger gennem en sådan visning er understøttet. Kør følgende kode for at slette alle nuværende fem seneste ordrer:

SLET FRA dbo.RecentOrders;

Kommandoen fuldføres.

Forespørg i tabellen:

VÆLG * FRA dbo.Orders;

Du får følgende output efter sletning af ordrerne med ID 8, 11, 12, 13 og 14.

orderid orderdate shippeddate ------------ ---------- ----------1 2021-08-02 2021-08-042 2021 -08-02 2021-08-053 2021-08-04 2021-08-064 2021-08-26 NULL5 2021-08-27 NULL6 2021-08-05 2021-08-0179 020201-020201 0310 2021-08-02 2021-08-04

På dette tidspunkt skal du køre følgende kode til oprydning, før du kører eksemplerne i næste afsnit:

DELETE FROM dbo.Orders WHERE orderid> 5; DROP VISNING HVIS FINDER dbo.RecentOrders;

Deltager

Opdatering af en visning, der forbinder flere tabeller, er understøttet, så længe kun én af de underliggende basistabeller er påvirket af ændringen.

Overvej følgende visning, der forbinder ordrer og ordredetaljer som et eksempel:

OPRET ELLER ÆNDRING VISNING dbo.OrdersOrderDetailsAS VÆLG O.orderid, O.orderdate, O.shippeddate, OD.productid, OD.qty, OD.unitprice, OD.discount FRA dbo.Orders AS O INNER JOIN dbo.OrderDetails AS OD ON O.orderid =OD.orderid;GO

Prøv at indsætte en række gennem visningen, så begge underliggende basistabeller vil blive påvirket:

INSERT INTO dbo.OrdersOrderDetails(orderid, orderdate, shippeddate, productid, quty, unitprice, discount) VALUES(6, '20210828', NULL, 1001, 5, 10.50, 0.05);

Du får følgende fejlmeddelelse:

Msg 4405, Level 16, State 1, Line 306
Vis eller funktion 'dbo.OrdersOrderDetails' kan ikke opdateres, fordi ændringen påvirker flere basistabeller.

Prøv at indsætte en række gennem visningen, så kun ordretabellen vil blive påvirket:

INSERT INTO dbo.OrdersOrderDetails(orderid, orderdate, shippeddate) VALUES(6, '20210828', NULL);

Denne kommando fuldføres med succes, og rækken indsættes i den underliggende ordretabel.

Men hvad hvis du også ønsker at kunne indsætte en række gennem visningen i OrderDetails-tabellen? Med den nuværende visningsdefinition er dette umuligt (i stedet for triggere til side), da visningen returnerer kolonnen orderid fra tabellen Ordrer og ikke fra tabellen OrderDetails. Det er tilstrækkeligt, at en kolonne fra OrderDetails-tabellen, der på en eller anden måde ikke kan få sin værdi automatisk, ikke er en del af visningen for at forhindre indsættelser i OrderDetails gennem visningen. Du kan selvfølgelig altid beslutte, at visningen skal indeholde både orderid fra Orders og orderid fra OrderDetails. I et sådant tilfælde bliver du nødt til at tildele de to kolonner forskellige aliaser, da overskriften på tabellen repræsenteret af visningen skal have unikke kolonnenavne.

Brug følgende kode til at ændre visningsdefinitionen til at inkludere begge kolonner, alias den fra Orders as O_orderid og den fra OrderDetails som OD_orderid:

OPRET ELLER ÆNDRING AF VISNING dbo.OrdersOrderDetailsAS SELECT O.orderid AS O_orderid, O.orderdate, O.shippeddate, OD.orderid AS OD_orderid,OD.productid, OD.qty, OD.unitprice, OD.discount FROM dbo.Orders AS O INNER JOIN dbo.OrderDetails AS OD ON O.orderid =OD.orderid;GO

Nu kan du indsætte rækker gennem visningen enten til Ordrer eller til OrderDetails, afhængigt af hvilken tabel målkolonnelisten kommer fra. Her er et eksempel på indsættelse af et par ordrelinjer forbundet med ordre 6 gennem visningen i OrderDetails:

INSERT INTO dbo.OrdersOrderDetails(OD_orderid, productid, qty, unitprice, rabat) VALUES(6, 1001, 5, 10,50, 0,05), (6, 1002, 5, 20,00, 0,05);

Rækkerne er tilføjet.

Spørg visningen:

SELECT * FROM dbo.OrdersOrderDetails WHERE O_orderid =6;

Du får følgende output:

O_orderid orderdate shippeddate OD_orderid produkt-id antal enhedspris rabat-------------------------------------------------- ---- ----------- ---- ---------- ----------6 2021-08-28 NULL 6 1001 5 10,50 0,05006 2021-08-28 NULL 6 1002 5 20,00 0,0500

En lignende begrænsning gælder for UPDATE-udsagn gennem visningen. Opdateringer er tilladt, så længe kun én underliggende basistabel er påvirket. Men du har lov til at henvise til kolonner fra begge sider i erklæringen, så længe kun den ene side bliver ændret.

Som et eksempel angiver følgende UPDATE-sætning gennem visningen ordredatoen for rækken, hvor ordrelinjens ordre-id er 6, og produkt-id'et er 1001 til "20210901:"

OPDATERING dbo.OrdersOrderDetails SET orderdate ='20210901' WHERE OD_orderid =6 OG productid =1001;

Vi kalder denne erklæring Opdateringserklæring 1.

Opdateringen fuldføres med følgende meddelelse:

(1 række påvirket)

Det, der er vigtigt at bemærke her, er, at erklæringen filtrerer efter elementer fra OrderDetails-tabellen, men den ændrede kolonne-ordredato er fra Order-tabellen. Så i den plan, SQL Server bygger for denne sætning, skal den finde ud af, hvilke ordrer der skal ændres i ordretabellen. Planen for denne erklæring er vist i figur 1.

Figur 1:Plan for Update-erklæring 1

Du kan se, hvordan planen starter ved at filtrere OrderDetails-siden efter både orderid =6 og productid =1001, og ordrerne side ved orderid =6, der forbinder de to. Resultatet er kun én række. Den eneste relevante del at holde fra denne aktivitet er, hvilke ordre-id'er i tabellen Ordrer repræsenterer rækker, der skal opdateres. I vores tilfælde er det ordren med ordre-ID 6. Derudover forbereder Compute Scalar-operatøren et medlem kaldet Expr1002 med den værdi, som erklæringen tildeler ordredatokolonnen i målordren. Den sidste del af planen med Clustered Index Update-operatoren anvender den faktiske opdatering til rækken i Ordrer med ordre-id 6, idet dens ordredato-værdi indstilles til Udtr1002.

Det vigtigste punkt at understrege her er, at kun én række med ordre-id 6 i ordretabellen er blevet opdateret. Alligevel har denne række to matches i resultatet af sammenkædningen med OrderDetails-tabellen – en med produkt-id 1001 (som den oprindelige opdatering filtrerede) og en anden med produkt-id 1002 (som den oprindelige opdatering ikke filtrerede). Forespørg på visningen på dette tidspunkt, filtrer alle rækker med ordre-id 6:

SELECT * FROM dbo.OrdersOrderDetails WHERE O_orderid =6;

Du får følgende output:

O_orderid orderdate shippeddate OD_orderid produkt-id antal enhedspris rabat-------------------------------------------------- ---- ----------- ---- ---------- ----------6 2021-09-01 NULL 6 1001 5 10,50 0,05006 2021-09-01 NULL 6 1002 5 20,00 0,0500

Begge rækker viser den nye ordredato, selvom den oprindelige opdatering kun filtrerede rækken med produkt-id 1001. Endnu en gang burde dette virke helt fornuftigt, hvis du ved, at du interagerer med en visning, der forbinder to basistabeller under omslagene, men kunne virke meget mærkeligt, hvis du ikke er klar over dette.

Mærkeligt nok understøtter SQL Server endda ikke-deterministiske opdateringer, hvor flere kilderækker (fra OrderDetails i vores tilfælde) matcher en enkelt målrække (i ordrer i vores tilfælde). Teoretisk set ville en måde at håndtere en sådan sag på være at afvise den. Faktisk, med en MERGE-sætning, hvor flere kilderækker matcher én målrække, afviser SQL Server forsøget. Men ikke med en OPDATERING baseret på en joinforbindelse, enten direkte eller indirekte gennem et navngivet tabeludtryk som en visning. SQL Server håndterer det simpelthen som en ikke-deterministisk opdatering.

Overvej følgende eksempel, som vi vil referere til som Udsagn 2:

OPDATERING dbo.OrdersOrderDetails SET orderdate =CASE WHEN unitprice>=20,00 THEN '20210902' ELSE '20210903' END WHERE OD_orderid =6;

Forhåbentlig vil du tilgive mig, at det er et konstrueret eksempel, men det illustrerer pointen.

Der er to kvalificerende rækker i visningen, der repræsenterer to kvalificerende kildeordrerækker fra den underliggende OrderDetails-tabel. Men der er kun én kvalificerende målrække i den underliggende ordretabel. Desuden returnerer det tildelte CASE-udtryk én værdi ('20210902') i den ene kilderække OrderDetails, og i den anden række OrderDetails returnerer det en anden værdi ('20210903'). Hvad skal SQL Server gøre i dette tilfælde? Som nævnt vil en lignende situation med MERGE-sætningen resultere i en fejl, der afviser forsøget på ændring. Men med en UPDATE-sætning slår SQL Server simpelthen en mønt. Teknisk set gøres dette ved hjælp af en intern aggregeret funktion kaldet ANY.

Så vores opdatering fuldføres med succes og rapporterer, at 1 række er påvirket. Planen for denne erklæring er vist i figur 2.


Figur 2:Plan for Update-erklæring 2

Der er to rækker i resultatet af sammenføjningen. Disse to rækker bliver kilderækkerne for opdateringen. Men så vælger en aggregeret operator, der anvender ANY-funktionen, én (en hvilken som helst) orderid-værdi og én (en hvilken som helst) enhedsprisværdi fra disse kilderækker. Begge kilderækker har samme orderid-værdi, så den rigtige rækkefølge vil blive ændret. Men afhængigt af hvilken af ​​kildeenhedsprisværdierne, ANY-aggregatet ender med at vælge, vil dette bestemme, hvilken værdi CASE-udtrykket vil returnere, for derefter at blive brugt som den opdaterede ordredatoværdi i målrækkefølgen. Du kan helt sikkert se et argument imod at understøtte en sådan opdatering, men det er fuldt understøttet i SQL Server.

Lad os forespørge i visningen for at se resultatet af denne ændring (nu er det tid til at foretage dit væddemål med hensyn til resultatet):

SELECT * FROM dbo.OrdersOrderDetails WHERE O_orderid =6;

Jeg fik følgende output:

O_orderid orderdate shippeddate OD_orderid produkt-id antal enhedspris rabat-------------------------------------------------- ---- ---------- ---- ---------- ----------6 2021-09-03 NULL 6 1001 5 10,50 0,05006 2021-09-03 NULL 6 1002 5 20,00 0,0500

Kun én af de to kildeenhedsprisværdier blev valgt og brugt til at bestemme ordredatoen for den enkelte målordre, men når der forespørges på visningen, gentages ordredatoværdien for begge matchende ordrelinjer. Som du kan indse, kunne resultatet lige så godt have været den anden dato (2021-09-02), da valget af enhedsprisværdien var ikke-deterministisk. Skøre ting!

Så under visse betingelser er INSERT- og UPDATE-sætninger tilladt gennem visninger, der forbinder flere underliggende tabeller. Sletninger er dog ikke tilladt mod sådanne synspunkter. Hvordan kan SQL Server fortælle, hvilken af ​​siderne der formodes at være målet for sletningen?

Her er et forsøg på at anvende en sådan sletning gennem visningen:

DELETE FROM dbo.OrdersOrderDetails WHERE O_orderid =6;

Dette forsøg er afvist med følgende fejl:

Msg 4405, Level 16, State 1, Line 377
Vis eller funktion 'dbo.OrdersOrderDetails' kan ikke opdateres, fordi ændringen påvirker flere basistabeller.

På dette tidspunkt skal du køre følgende kode til oprydning:

DELETE FROM dbo.OrderDetails WHERE orderid =6;DELETE FROM dbo.Orders WHERE orderid =6;DROP VISNING HVIS FINDER dbo.OrdersOrderDetails;

Afledte kolonner

En anden begrænsning for ændringer gennem visninger har at gøre med afledte kolonner. Hvis en visningskolonne er et resultat af en beregning, vil SQL Server ikke forsøge at ændre dens formel, når du forsøger at indsætte eller opdatere data gennem visningen – den vil snarere afvise sådanne ændringer.

Betragt følgende visning som et eksempel:

OPRET ELLER ÆNDRING AF VISNING dbo.OrderDetailsNetPriceAS SELECT orderid, productid, quty, unitprice * (1,0 - rabat) SOM netunitprice, rabat FRA dbo.OrderDetails;GO

Visningen beregner kolonnen nettoenhedspris baseret på de underliggende OrderDetails tabelkolonner enhedspris og rabat.

Spørg visningen:

VÆLG * FRA dbo.OrderDetailsNetPrice;

Du får følgende output:

orderid produkt-id antal netenhedspris rabat-------------------------------------------- ---- --------- 1 1001 5 9.975000 0.05001 1004 2 20.000000 0.00002 1003 1 47.691000 0.10003 1001 1 9.975000 0.05003 1003 2 49.491000 0.10004 1001 2 9.975000 0.05004 100.30 1006 2 11,316000 0,0800

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

INSERT INTO dbo.OrderDetailsNetPrice(orderid, productid, quty, netunitprice, discount) VALUES(1, 1005, 1, 28.595, 0.05);

Teoretisk set kan du finde ud af, hvilken række der skal indsættes i den underliggende OrderDetails-tabel ved at omvendt manipulere basistabellens enhedsprisværdi ud fra visningens netenhedspris og rabatværdier. SQL Server forsøger ikke sådan reverse engineering, men afviser forsøget på indsættelse med følgende fejl:

Msg 4406, Level 16, State 1, Line 412
Opdatering eller indsættelse af visning eller funktion 'dbo.OrderDetailsNetPrice' mislykkedes, fordi den indeholder et afledt eller konstant felt.

Prøv at udelade den beregnede kolonne fra indsættelsen:

INSERT INTO dbo.OrderDetailsNetPrice(orderid, productid, quty, discount) VALUES(1, 1005, 1, 0,05);

Nu er vi tilbage til kravet om, at alle kolonner fra den underliggende tabel, der på en eller anden måde ikke automatisk får deres værdier, skal være en del af indsættelsen, og her mangler vi enhedspriskolonnen. Denne indsættelse mislykkes med følgende fejl:

Msg 515, Level 16, State 2, Line 421
Cannot insert the value NULL into column 'unitprice', table 'tempdb.dbo.OrderDetails'; column does not allow nulls. INSERT fails.

If you want to support insertions through the view, you basically have two options. One is to include the unitprice column in the view definition. Another is to create an instead of trigger on the view where you handle the reverse engineering logic yourself.

At this point, run the following code for cleanup:

DROP VIEW IF EXISTS dbo.OrderDetailsNetPrice;

Set Operators

As mentioned in the last section, you’re not allowed to modify a column in a view if the column is a result of a computation. The columns modified in the view using INSERT and UPDATE statements have to map directly to the underlying base table’s columns with no manipulation. In the list of restrictions to modifications through views, T-SQL’s documentation specifies that columns formed by using the set operators UNION, UNION ALL, EXCEPT, and INTERSECT amount to a computation and therefore are also not updatable.

One exception to this restriction is when using the UNION ALL operator to combine rows from different tables to form an updatable partitioned view. That’s a big topic in its own right. I’ll cover it briefly here to give you a sense, and you can investigate it further if you like in the product’s documentation.

Partitioned views predates table and index partitioning in SQL Server. The basic idea is that you can store disjoint subsets of rows in different base tables and have a view that unifies the rows from the different tables using a UNION ALL operator. If certain requirements are met, you can not only read the data through the view but also modify it through the view. SQL Server will figure out how to direct the modifications through the view to the right underlying tables.

The requirements for supporting modifications through such a view include having a partitioning column. Each of the underlying tables needs to have a CHECK constraint based on the partitioning column that defines a disjoint subset of rows. Also, the partitioning column needs to be part of the table’s primary key, meaning it cannot allow NULLs.

Consider the Orders table you used earlier in this article. Suppose that instead of holding all orders in one table, you want to store unshipped orders in one table (called UnshippedOrders) and shipped orders in another table (called ShippedOrders). You also want to create a view called Orders combining the rows from both tables. You want the view to be updatable.

Let’s start by removing any existing objects before creating the new ones:

DROP VIEW IF EXISTS dbo.Orders;DROP TABLE IF EXISTS dbo.OrderDetails, dbo.Orders;DROP TABLE IF EXISTS dbo.ShippedOrders, dbo.UnshippedOrders;

The partitioning column in our example is the shippeddate column. Our first obstacle is that we want to represent unshipped orders with a NULL shippeddate, but the partitioning column cannot allow NULLs. One possible workaround is to decide on some specific future date to represent unshipped orders. For example, the maximum supported date December 31st, 9999. Then you could have a CHECK constraint in the UnshippedOrders table checking that the shipped date is this specific one, and a CHECK constraint in the ShippedOrders table checking that the shipped date is before this one. This will meet the requirement for disjoint sets of rows.

Another obstacle is that the partitioning column needs to be part of the primary key. Originally the primary key was based on the orderid column alone. Now it will need to be extended to be based on (orderid, shippeddate). You will probably still want to enforce uniqueness based on orderid alone. To achieve this, you’ll need to add a unique constraint based on orderid.

With all this in mind, here are the definitions of the ShippedOrders and UnshippedOrders tables:

CREATE TABLE dbo.ShippedOrders( orderid INT NOT NULL, orderdate DATE NOT NULL, shippeddate DATE NOT NULL, CONSTRAINT PK_ShippedOrders PRIMARY KEY(orderid, shippeddate), CONSTRAINT UNQ_ShippedOrders_orderid UNIQUE(orderid), CONSTRAINT CHK_ShippedOrders_shippeddate CHECK(shippeddate <'99991231')); CREATE TABLE dbo.UnshippedOrders( orderid INT NOT NULL, orderdate DATE NOT NULL, shippeddate DATE NOT NULL DEFAULT('99991231'), CONSTRAINT PK_UnshippedOrders PRIMARY KEY(orderid, shippeddate), CONSTRAINT UNQ_UnshippedOrders_orderid UNIQUE(orderid), CONSTRAINT CHK_UnshippedOrders_shippeddate CHECK(shippeddate ='99991231'));

You then create the Orders view, unifying the rows from the two tables using the UNION ALL operator, like so:

CREATE OR ALTER VIEW dbo.OrdersAS SELECT orderid, orderdate, shippeddate FROM dbo.ShippedOrders UNION ALL SELECT orderid, orderdate, shippeddate FROM dbo.UnshippedOrders;GO

Since this view meets all requirements for updatability, you can insert, update, and delete rows through the view. SQL Server will direct the changes to the right underlying tables. As an example, the following statement inserts a few rows, including both shipped and unshipped orders:

INSERT INTO dbo.Orders(orderid, orderdate, shippeddate) VALUES(1, '20210802', '20210804'), (2, '20210802', '20210805'), (3, '20210804', '20210806'), (4, '20210826', '99991231'), (5, '20210827', '99991231');

The plan for this code is shown in Figure 3.

Figure 3:Plan for INSERT statement against partitioned view

As you can see, a Compute Scalar operator computes for each source row a member called Ptn1018. This member is set to 0 for shipped orders (shippeddate <'9999-12-31') and 1 for unshipped orders (shippeddate ='9999-12-31'). The rows are spooled along with the member Ptn1018, and then the spool is read twice. Once filtering the rows where Ptn1018 =0, inserting those into the underlying ShippedOrders table, and another time filtering the rows where Ptn1018 =1, inserting those into the underlying UnshippedOrders table.If this seems like an attractive option, consider it very carefully. Remember this is an old feature, predating table and index partitioning. There are many requirements, restrictions, and complications, including optimization complications, integrity enforcement complications, and others. As mentioned, here I just wanted to cover it briefly to describe the exception to the modification restriction involving set operators.When you’re done, run the following code for cleanup:

DROP VIEW IF EXISTS dbo.Orders;DROP TABLE IF EXISTS dbo.OrderDetails, dbo.Orders;DROP TABLE IF EXISTS dbo.ShippedOrders, dbo.UnshippedOrders;

Oversigt

When I started the coverage of views, one of the first things I explained was that a view is a table. You can read data from a view and you can modify data through a view. But you need to understand that modifications through the view are restricted in a few ways, and the outcome of such modifications could be surprising in some cases.

Using the CHECK OPTION, you’re only allowed to update and insert rows through the view as long as the result rows are considered a valid part of the view. This means unlike a CHECK constraint in a table, the CHECK OPTION rejects changes where the inner query’s filter evaluates to unknown (when a NULL is involved). You’re not allowed to insert or update rows through a view if it’s defined with the CHECK OPTION and uses the TOP or OFFSET-FETCH filters. But you’re allowed to delete rows through such a view.

If a view joins multiple base tables, inserts and updates through the view are allowed provided that only one underlying base table is affected. Oddly, if a modification of a single target row involves multiple related source rows, the modification is allowed but is processed as a nondeterministic one. In such a case, SQL Server uses the internal ANY aggregate the pick a single value from the source rows.

You cannot update or insert rows through a view where at least one of the updated columns is a derived one resulting from a computation. The same applies when using a set operator, with an exception when using the UNION ALL operator to create an updatable partitioned view.


  1. Forskellen mellem LIKE og =i MYSQL?

  2. Hvordan konfigureres Ruby on Rails med Oracle?

  3. Oracle dato til streng konvertering

  4. IllegalStateException:database allerede lukket (ved hjælp af ViewPager)