sql >> Databasteknik >  >> RDS >> Database

Fundamentals of tabelluttryck, del 11 – vyer, modifieringsöverväganden

Den här artikeln är den elfte delen i en serie om tabelluttryck. Hittills har jag täckt härledda tabeller och CTE:er och nyligen börjat täcka visningar. I del 9 jämförde jag vyer med härledda tabeller och CTE, och i del 10 diskuterade jag DDL-ändringar och konsekvenserna av att använda SELECT * i vyns inre fråga. I den här artikeln fokuserar jag på modifieringsöverväganden.

Som du säkert vet har du rätt att modifiera data i bastabeller indirekt genom namngivna tabelluttryck som vyer. Du kan styra ändringsbehörigheter mot vyer. Faktum är att du kan ge användarna behörighet att ändra data genom vyer utan att ge dem behörighet att ändra de underliggande tabellerna direkt.

Du måste vara medveten om vissa komplexiteter och begränsningar som gäller för ändringar genom vyer. Intressant nog kan vissa av de modifieringar som stöds sluta med överraskande resultat, särskilt om användaren som ändrar data inte är medveten om att de interagerar med en vy. Du kan införa ytterligare begränsningar för ändringar genom vyer genom att använda ett alternativ som heter CHECK OPTION, som jag kommer att täcka i den här artikeln. Som en del av täckningen kommer jag att beskriva en märklig inkonsekvens mellan hur CHECKALTERNATIVET i en vy och en CHECK-begränsning i en tabell hanterar ändringar – särskilt sådana som involverar NULL.

Exempeldata

Som exempeldata för den här artikeln kommer jag att använda tabeller som heter Orders and OrderDetails. Använd följande kod för att skapa dessa tabeller i tempdb och fylla i dem med några initiala exempeldata:

ANVÄND tempdb;GO DROP TABLE OM FINNS 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'), (8 4, '20210826', NULL), (5, '20210827', NULL); CREATE TABLE dbo.OrderDetails( orderid INT NOT NULL CONSTRAINT FK_OrderDetails_Orders REFERENCES dbo.Orders, productid INT NOT NULL, quty INT NOT NULL, unitprice NUMERIC(12, 2) NOT NULL, discount NUMERIC(5, PKMA) KEY(orderid, produktid)); INSERT INTO dbo.OrderDetails(orderid, productid, quty, unitprice, rabatt) VALUES(1, 1001, 5, 10,50, 0,05), (1, 1004, 2, 20,00, 0,00), (2, 1003, 59, 5 0,10), (3, 1001, 1, 10,50, 0,05), (3, 1003, 2, 54,99, 0,10), (4, 1001, 2, 10,50, 0,05), (4, 1004, 1, 0, 0,0) , (4, 1005, 1, 30,10, 0,05), (5, 1003, 5, 54,99, 0,00), (5, 1006, 2, 12,30, 0,08);

Tabellen Order innehåller orderrubriker och tabellen OrderDetails innehåller orderrader. Olevererade beställningar har en NULL i kolumnen shippeddate. Om du föredrar en design som inte använder NULLs, kan du använda ett specifikt framtida datum för ej skickade beställningar, till exempel "99991231."

KOCKERA ALTERNATIV

För att förstå omständigheterna där du skulle vilja använda KONTROLLOPTIONEN som en del av en vys definition, undersöker vi först vad som kan hända när du inte använder det.

Följande kod skapar en vy som heter FastOrders som representerar beställningar som har skickats inom sju dagar sedan de lades:

SKAPA ELLER ÄNDRA VISA dbo.FastOrdersAS SELECT orderid, orderdate, shippeddate FROM dbo.Orders WHERE DATEDIFF(day, orderdate, shippeddate) <=7;GO

Använd följande kod för att infoga en beställning som skickas två dagar efter att den har lagts genom vyn:

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

Fråga vyn:

VÄLJ * FRÅN dbo.FastOrders;

Du får följande utdata, som inkluderar den nya beställningen:

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

Fråga den underliggande tabellen:

VÄLJ * FRÅN dbo.Orders;

Du får följande utdata, som inkluderar den nya beställningen:

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

Raden infogades i den underliggande bastabellen genom vyn.

Infoga sedan genom vyn en rad som skickades 10 dagar efter att den placerades, vilket motsäger vyns inre frågefilter:

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

Uttalandet slutförs framgångsrikt och rapporterar att en rad påverkas.

Fråga vyn:

VÄLJ * FRÅN dbo.FastOrders;

Du får följande utdata, som exkluderar den nya beställningen:

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

Om du vet att FastOrders är en vy, kan allt detta verka vettigt. När allt kommer omkring infogades raden i den underliggande tabellen och den uppfyller inte vyns inre frågefilter. Men om du inte är medveten om att FastOrders är en vy och inte en bastabell, skulle detta beteende verka förvånande.

Fråga den underliggande ordertabellen:

VÄLJ * FRÅN dbo.Orders;

Du får följande utdata, som inkluderar den nya beställningen:

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 uppleva ett liknande överraskande beteende om du genom vyn uppdaterar värdet på shippeddate i en rad som för närvarande är en del av vyn till ett datum som gör att det inte längre kvalificerar sig som en del av vyn. En sådan uppdatering är normalt tillåten, men återigen sker den i den underliggande bastabellen. Om du frågar vyn efter en sådan uppdatering verkar den ändrade raden vara borta. I praktiken finns det fortfarande kvar i den underliggande tabellen, det anses bara inte vara en del av vyn längre.

Kör följande kod för att ta bort raderna du lade till tidigare:

DELETE FROM dbo.Orders WHERE orderid>=6;

Om du vill förhindra ändringar som kommer i konflikt med vyns inre frågefilter, lägg till MED KONTROLLOPTION i slutet av den inre frågan som en del av vyns definition, så här:

SKAPA ELLER ÄNDRA VISA dbo.FastOrdersAS SELECT orderid, orderdate, shippeddate FRÅN dbo.Orders WHERE DATEDIFF(dag, orderdatum, shippeddate) <=7 MED KONTROLLOPTION;GO

Infogar och uppdateringar genom vyn är tillåtna så länge de överensstämmer med den inre frågans filter. Annars avvisas de.

Använd till exempel följande kod för att infoga en rad genom vyn som inte står i konflikt med det inre frågefiltret:

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

Raden har lagts till.

Försök att infoga en rad som inte är i konflikt med filtret:

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

Den här gången avvisas raden med följande fel:

Nivå 16, tillstånd 1, rad 135
Försöket att infoga eller uppdatera misslyckades eftersom målvyn antingen anger WITH CHECK OPTION eller sträcker sig över en vy som anger WITH CHECK OPTION och en eller flera rader som härrörde från operationen kvalificerade sig inte enligt KONTROLLERA ALTERNATIV begränsning.

NULL Inkonsekvenser

Om du har arbetat med T-SQL under en tid är du förmodligen väl medveten om ovannämnda modifieringskomplexitet och funktionen CHECK OPTION tjänar. Ofta tycker även erfarna personer att NULL-hanteringen av KONTROLLOPTIONEN är överraskande. I flera år brukade jag tänka på KONTROLLOPTIONEN i en vy som tjänar samma funktion som en CHECK-begränsning i en bastabells definition. Det är också så jag brukade beskriva det här alternativet när jag skrev eller undervisade om det. Så länge det inte finns några NULLs inblandade i filterpredikatet är det faktiskt bekvämt att tänka på de två i liknande termer. De beter sig konsekvent i ett sådant fall – accepterar rader som överensstämmer med predikatet och förkastar de som står i konflikt med det. De två hanterar dock NULLs inkonsekvent.

När du använder KONTROLLOPTIONEN tillåts en modifiering genom vyn så länge som predikatet utvärderas till sant, annars avvisas det. Detta innebär att det avvisas när vyns predikat utvärderas till falskt eller okänt (när en NULL är inblandad). Med en CHECK-begränsning tillåts modifieringen när begränsningens predikat utvärderas till sant eller okänt, och avvisas när predikatet utvärderas till falskt. Det är en intressant skillnad! Låt oss först se detta i praktiken, sedan ska vi försöka ta reda på logiken bakom denna inkonsekvens.

Försök att infoga en rad genom vyn med ett NULL-leveransdatum:

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

Vyns predikat utvärderas till okänt, och raden avvisas med följande fel:

Msg 550, Level 16, State 1, Line 147
Försöket att infoga eller uppdatera misslyckades eftersom målvyn antingen specificerar WITH CHECK OPTION eller sträcker sig över en vy som specificerar WITH CHECK OPTION och en eller flera rader som härrörde från operationen inte kvalificera sig under begränsningen KONTROLLOPTION.

Låt oss prova en liknande infogning mot en bastabell med en CHECK-begränsning. Använd följande kod för att lägga till en sådan begränsning till vår orders tabelldefinition:

ÄNDRA TABELL dbo.Order ADD CONSTRAINT CHK_Orders_FastOrder CHECK(DATEDIFF(day, orderdate, shippeddate) <=7);

Först, för att se till att begränsningen fungerar när det inte finns några NULLs inblandade, försök att infoga följande beställning med ett leveransdatum 10 dagar från beställningsdatumet:

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

Detta försök till infogning avvisas med följande fel:

Msg 547, Level 16, State 0, Line 159
INSERT-satsen stod i konflikt med CHECK-begränsningen "CHK_Orders_FastOrder". Konflikten inträffade i databasen "tempdb", tabellen "dbo.Orders".

Använd följande kod för att infoga en rad med ett NULL leveransdatum:

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

En CHECK-begränsning ska avvisa falska fall, men i vårt fall utvärderas predikatet till okänt, så raden läggs till framgångsrikt.

Fråga i ordertabellen:

VÄLJ * FRÅN dbo.Orders;

Du kan se den nya ordningen i utgången:

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

Vad är logiken bakom denna inkonsekvens? Du kan argumentera för att en CHECK-begränsning endast bör tillämpas när begränsningens predikat tydligt överträds, alltså när det utvärderas till falskt. På detta sätt, om du väljer att tillåta NULL i kolumnen i fråga, tillåts rader med NULL i kolumnen även om begränsningens predikat utvärderas till okänt. I vårt fall representerar vi ej skickade beställningar med en NULL i kolumnen shippeddate, och vi tillåter ej levererade beställningar i tabellen samtidigt som vi tillämpar regeln för "snabbbeställningar" endast för skickade beställningar.

Argumentet för att använda annan logik med en vy är att en modifiering endast bör tillåtas genom vyn om resultatraden är en giltig del av vyn. Om vyns predikat utvärderas till okänt, t.ex. när leveransdatumet är NULL, är resultatraden inte en giltig del av vyn, och därför avvisas den. Endast rader för vilka predikatet utvärderas till sant är en giltig del av vyn och är därför tillåtna.

NULL lägger till mycket komplexitet till språket. Gilla dem eller inte, om din data stöder dem, vill du vara säker på att du förstår hur T-SQL hanterar dem.

Vid det här laget kan du släppa KONTROLL-begränsningen från ordertabellen och även släppa FastOrders-vyn för rensning:

ÄNDRA TABELL dbo.Orders DROP CONSTRAINT CHK_Orders_FastOrder;DROP VIEW OM FINNS dbo.FastOrders;

TOPP/OFFSET-HÄMTNING Begränsning

Modifieringar genom vyer som involverar TOP- och OFFSET-FETCH-filtren är normalt tillåtna. Men precis som med vår tidigare diskussion om vyer som definierats utan KONTROLLALTERNATIV, kan resultatet av en sådan ändring verka konstigt för användaren om de inte är medvetna om att de interagerar med en vy.

Betrakta följande vy som representerar de senaste beställningarna som ett exempel:

SKAPA ELLER ÄNDRA VISA dbo.RecentOrdersAS SELECT TOP (5) orderid, orderdate, shippeddate FRÅN dbo.Orders BESTÄLLNING EFTER orderdatum DESC, orderid DESC;GO

Använd följande kod för att infoga sex beställningar i vyn RecentOrders:

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

Fråga vyn:

VÄLJ * FRÅN dbo.RecentOrders;

Du får följande utdata:

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

Av de sex infogade beställningarna är endast fyra en del av vyn. Detta verkar helt förnuftigt om du är medveten om att du frågar efter en vy som är baserad på en fråga med ett TOP-filter. Men det kan tyckas konstigt om du tror att du frågar efter en bastabell.

Fråga direkt i den underliggande ordertabellen:

VÄLJ * FRÅN dbo.Orders;

Du får följande utdata som visar alla tillagda beställningar:

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 09208 09208 20208 -01 2021-08-0310 2021-08-02 2021-08-0411 2021-08-29 2021-08-3112 2021-08-30 2021-09-0213 2021-09-0213 2001-202-1-4 -31 2021-09-03

Om du lägger till KONTROLLOPTIONEN till vyns definition, kommer INSERT- och UPDATE-satser mot vyn att avvisas. Använd följande kod för att tillämpa denna ändring:

SKAPA ELLER ÄNDRA VISA dbo.RecentOrdersAS VÄLJ TOP (5) orderid, orderdate, shippeddate FRÅN dbo.Order BESTÄLL EFTER orderdatum DESC, orderid DESC MED KONTROLLOPTION;GO

Försök att lägga till en beställning via vyn:

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

Du får följande felmeddelande:

Msg 4427, Level 16, State 1, Line 247
Kan inte uppdatera vyn "dbo.RecentOrders" eftersom den eller en vy som den refererar till skapades med WITH CHECK OPTION och dess definition innehåller en TOP- eller OFFSET-sats.

SQL Server försöker inte vara för smart här. Det kommer att avvisa ändringen även om raden du försöker infoga skulle bli en giltig del av vyn vid den tidpunkten. Försök till exempel att lägga till en beställning med ett nyare datum som skulle hamna bland topp 5 vid denna tidpunkt:

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

Insättningsförsöket avvisas fortfarande med följande fel:

Msg 4427, Level 16, State 1, Line 254
Kan inte uppdatera vyn "dbo.RecentOrders" eftersom den eller en vy som den refererar till skapades med WITH CHECK OPTION och dess definition innehåller en TOP- eller OFFSET-sats.

Försök att uppdatera en rad genom vyn:

UPPDATERA dbo.RecentOrders SET shippeddate =DATEADD(dag, 2, orderdatum);

I det här fallet avvisas även försöket att ändra med följande fel:

Msg 4427, Level 16, State 1, Line 260
Kan inte uppdatera vyn "dbo.RecentOrders" eftersom den eller en vy som den refererar till skapades med WITH CHECK OPTION och dess definition innehåller en TOP- eller OFFSET-sats.

Tänk på att om du definierar en vy baserat på en fråga med TOP eller OFFSET-FETCH och CHECK OPTION kommer det att resultera i brist på stöd för INSERT- och UPDATE-satser genom vyn.

Borttagningar via en sådan vy stöds. Kör följande kod för att radera alla nuvarande fem senaste beställningar:

RADERA FRÅN dbo.RecentOrders;

Kommandot slutförs framgångsrikt.

Fråga tabellen:

VÄLJ * FRÅN dbo.Orders;

Du får följande utdata efter raderingen av beställningarna med ID 8, 11, 12, 13 och 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 8-2001 8-2001 0310 2021-08-02 2021-08-04

Kör nu följande kod för rengöring innan du kör exemplen i nästa avsnitt:

DELETE FROM dbo.Orders WHERE orderid> 5; SLÄPP VISNING OM FINNS dbo.RecentOrders;

Gå med

Uppdatering av en vy som sammanfogar flera tabeller stöds, så länge som bara en av de underliggande bastabellerna påverkas av ändringen.

Betrakta följande vy som går med i Order och OrderDetails som ett exempel:

SKAPA ELLER ÄNDRA VISA dbo.OrdersOrderDetailsAS SELECT O.orderid, O.orderdate, O.shippeddate, 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

Försök att infoga en rad genom vyn, så att båda underliggande bastabellerna skulle påverkas:

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öljande felmeddelande:

Msg 4405, Level 16, State 1, Line 306
Visa eller funktionen 'dbo.OrdersOrderDetails' kan inte uppdateras eftersom ändringen påverkar flera bastabeller.

Försök att infoga en rad genom vyn, så att endast ordertabellen påverkas:

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

Detta kommando slutförs framgångsrikt och raden infogas i den underliggande ordertabellen.

Men vad händer om du också vill kunna infoga en rad genom vyn i tabellen OrderDetails? Med den nuvarande vydefinitionen är detta omöjligt (istället för triggers åt sidan) eftersom vyn returnerar orderid-kolumnen från Order-tabellen och inte från OrderDetails-tabellen. Det räcker med att en kolumn från OrderDetails-tabellen som på något sätt inte kan få sitt värde automatiskt inte är en del av vyn för att förhindra infogning i OrderDetails genom vyn. Naturligtvis kan du alltid bestämma att vyn ska innehålla både orderid från Orders och orderid från OrderDetails. I ett sådant fall måste du tilldela de två kolumnerna olika alias eftersom rubriken på tabellen som representeras av vyn måste ha unika kolumnnamn.

Använd följande kod för att ändra vydefinitionen så att den inkluderar båda kolumnerna, alias den från Orders as O_orderid och den från OrderDetails som OD_orderid:

SKAPA ELLER ÄNDRA VISA 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 infoga rader genom vyn antingen till Orders eller OrderDetails, beroende på vilken tabell målkolumnlistan kommer från. Här är ett exempel för att infoga ett par orderrader associerade med order 6 genom vyn i OrderDetails:

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

Raderna har lagts till.

Fråga vyn:

SELECT * FROM dbo.OrdersOrderDetails WHERE O_orderid =6;

Du får följande utdata:

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

En liknande begränsning är tillämplig på UPDATE-satser genom vyn. Uppdateringar är tillåtna så länge som bara en underliggande bastabell påverkas. Men du får referera till kolumner från båda sidor i uttalandet så länge som bara en sida ändras.

Som ett exempel anger följande UPDATE-sats genom vyn beställningsdatumet för raden där beställningsradens beställnings-ID är 6 och produkt-ID:t är 1001 till "20210901:"

UPPDATERA dbo.OrdersOrderDetails SET orderdate ='20210901' WHERE OD_orderid =6 OCH productid =1001;

Vi kallar detta uttalande Uppdatering uttalande 1.

Uppdateringen slutförs framgångsrikt med följande meddelande:

(1 rad påverkad)

Vad som är viktigt att notera här är att uttalandet filtreras efter element från OrderDetails-tabellen, men den ändrade kolumnen orderdatum kommer från Order-tabellen. Så i planen som SQL Server bygger för detta uttalande måste den ta reda på vilka order som måste ändras i ordertabellen. Planen för detta uttalande visas i figur 1.

Figur 1:Plan för uppdatering uttalande 1

Du kan se hur planen börjar genom att filtrera OrderDetails-sidan efter både orderid =6 och productid =1001, och Orders-sidan efter orderid =6, förena de två. Resultatet är bara en rad. Den enda relevanta delen att behålla från denna aktivitet är vilka order-ID:n i tabellen Order representerar rader som behöver uppdateras. I vårt fall är det ordern med order-ID 6. Dessutom förbereder Compute Scalar-operatören en medlem som heter Expr1002 med det värde som uttalandet kommer att tilldela orderdatumkolumnen i målordern. Den sista delen av planen med Clustered Index Update-operatorn tillämpar den faktiska uppdateringen på raden i Orders with Order ID 6, och sätter dess orderdate-värde till Expr1002.

Den viktigaste punkten att betona här är att endast en rad med orderid 6 i ordertabellen har uppdaterats. Ändå har den här raden två matchningar i resultatet av sammankopplingen med OrderDetails-tabellen – en med produkt-ID 1001 (som den ursprungliga uppdateringen filtrerade) och en annan med produkt-ID 1002 (som den ursprungliga uppdateringen inte filtrerade). Fråga vyn nu och filtrera alla rader med order-ID 6:

SELECT * FROM dbo.OrdersOrderDetails WHERE O_orderid =6;

Du får följande utdata:

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

Båda raderna visar det nya beställningsdatumet, även om den ursprungliga uppdateringen endast filtrerade raden med produkt-ID 1001. Återigen bör detta verka helt förnuftigt om du vet att du interagerar med en vy som förenar två bastabeller under omslagen, men kan verka väldigt konstigt om du inte inser detta.

Märkligt nog stöder SQL Server till och med icke-deterministiska uppdateringar där flera källrader (från OrderDetails i vårt fall) matchar en enda målrad (i Orders i vårt fall). Teoretiskt sett skulle ett sätt att hantera ett sådant fall vara att avvisa det. Faktum är att med en MERGE-sats där flera källrader matchar en målrad, avvisar SQL Server försöket. Men inte med en UPPDATERING baserad på en join, vare sig direkt eller indirekt genom ett namngivet tabelluttryck som en vy. SQL Server hanterar det helt enkelt som en icke-deterministisk uppdatering.

Betrakta följande exempel, som vi kommer att referera till som uttalande 2:

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

Förhoppningsvis kommer du att förlåta mig att det är ett konstruerat exempel, men det illustrerar poängen.

Det finns två kvalificerande rader i vyn, som representerar två kvalificerande källorderrader från den underliggande OrderDetails-tabellen. Men det finns bara en kvalificerande målrad i den underliggande ordertabellen. Dessutom, i en käll OrderDetails-raden returnerar det tilldelade CASE-uttrycket ett värde ('20210902') och i den andra källraden OrderDetails returnerar det ett annat värde ('20210903'). Vad ska SQL Server göra i det här fallet? Som nämnts skulle en liknande situation med MERGE-satsen resultera i ett fel som avvisar försöket att ändra. Men med en UPDATE-sats slår SQL Server helt enkelt ett mynt. Tekniskt sett görs detta med en intern aggregatfunktion som kallas ANY.

Så vår uppdatering slutförs framgångsrikt och rapporterar att 1 rad påverkas. Planen för detta uttalande visas i figur 2.


Figur 2:Plan för uppdatering uttalande 2

Det finns två rader i resultatet av sammanfogningen. Dessa två rader blir källraderna för uppdateringen. Men sedan väljer en aggregerad operator som använder funktionen ANY ett (valfritt) orderid-värde och ett (valfritt) enhetsprisvärde från dessa källrader. Båda källraderna har samma orderid-värde, så rätt ordning kommer att ändras. Men beroende på vilket av källenhetsprisvärdena som ANY-aggregatet slutar välja, kommer detta att avgöra vilket värde CASE-uttrycket kommer att returnera, för att sedan användas som det uppdaterade orderdate-värdet i målordningen. Du kan säkert se ett argument mot att stödja en sådan uppdatering, men det stöds fullt ut i SQL Server.

Låt oss fråga vyn för att se resultatet av denna förändring (nu är det dags att göra din insats för resultatet):

SELECT * FROM dbo.OrdersOrderDetails WHERE O_orderid =6;

Jag fick följande utdata:

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

Endast ett av de två källenhetsprisvärdena valdes ut och användes för att bestämma orderdatumet för den enda målordern, men när man frågar vyn upprepas orderdatumvärdet för båda matchande orderraderna. Som ni förstår kunde utfallet lika gärna ha blivit det andra datumet (2021-09-02) eftersom valet av enhetsprisvärdet var obestämt. Galna grejer!

Så under vissa förhållanden är INSERT- och UPDATE-satser tillåtna genom vyer som sammanfogar flera underliggande tabeller. Borttagningar är dock inte tillåtna mot sådana synpunkter. Hur kan SQL Server avgöra vilken av sidorna som är tänkt att vara målet för borttagningen?

Här är ett försök att tillämpa en sådan radering genom vyn:

DELETE FROM dbo.OrdersOrderDetails WHERE O_orderid =6;

Detta försök avvisas med följande fel:

Msg 4405, Level 16, State 1, Line 377
Visa eller funktionen 'dbo.OrdersOrderDetails' är inte uppdateringsbar eftersom ändringen påverkar flera bastabeller.

Kör nu följande kod för rengöring:

DELETE FROM dbo.OrderDetails WHERE orderid =6;DELETE FROM dbo.Orders WHERE orderid =6;DROP VIEW OM FINNS dbo.OrdersOrderDetails;

Härledda kolumner

En annan begränsning för ändringar genom vyer har att göra med härledda kolumner. Om en vykolumn är ett resultat av en beräkning kommer SQL Server inte att försöka omvända dess formel när du försöker infoga eller uppdatera data genom vyn – snarare kommer den att avvisa sådana ändringar.

Betrakta följande vy som ett exempel:

SKAPA ELLER ÄNDRA VISA dbo.OrderDetailsNetPriceAS SELECT orderid, productid, quty, unitprice * (1,0 - rabatt) SOM netunitprice, rabatt FRÅN dbo.OrderDetails;GO

Vyn beräknar nettoenhetspriskolumnen baserat på de underliggande OrderDetails-tabellkolumnerna enhetspris och rabatt.

Fråga vyn:

VÄLJ * FRÅN dbo.OrderDetailsNetPrice;

Du får följande utdata:

orderid produkt-id antal nettoenhetsprisrabatt-------------------------------------------------- ---- ---------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 1004 1 20.300000 0.00004 1005 1 28.595000 0.05005 1003 5 54.990000 0.00005 1006 2 11,316000 0,0800

Försök att infoga en rad genom vyn:

INSERT INTO dbo.OrderDetailsNetPrice(orderid, productid, quty, netunitprice, rabatt) VALUES(1, 1005, 1, 28,595, 0,05);

Teoretiskt sett kan du ta reda på vilken rad som behöver infogas i den underliggande OrderDetails-tabellen genom att omvänd manipulera bastabellens enhetsprisvärde från vyns nettoenhetspris och rabattvärden. SQL Server försöker inte göra omvänd konstruktion, men avvisar försöket att infoga med följande fel:

Msg 4406, Level 16, State 1, Line 412
Uppdatering eller infogning av vy eller funktion 'dbo.OrderDetailsNetPrice' misslyckades eftersom den innehåller ett härlett eller konstant fält.

Försök att utelämna den beräknade kolumnen från infogningen:

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

Nu är vi tillbaka till kravet att alla kolumner från den underliggande tabellen som inte på något sätt får sina värden automatiskt måste vara en del av infogningen, och här saknar vi enhetspriskolumnen. Denna infogning misslyckas med följande fel:

Msg 515, Level 16, State 2, Line 421
Kan inte infoga värdet NULL i kolumn 'unitprice', tabell '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;

Sammanfattning

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. Skapa ett SQL Server Agent Job med SSMS

  2. Hur deklarerar man en variabel i MySQL?

  3. arbeta med json i oracle

  4. 1052:Kolumn 'id' i fältlistan är tvetydig