sql >> Databasteknik >  >> RDS >> Database

Grundläggande tabelluttryck, del 10 – Visningar, SELECT * och DDL-ändringar

Som en del av serien om tabelluttryck började jag förra månaden bevakningen av åsikter. Specifikt började jag täckningen av logiska aspekter av vyer och jämförde deras design med den för härledda tabeller och CTE:er. Den här månaden kommer jag att fortsätta bevakningen av logiska aspekter av åsikter, och fokusera min uppmärksamhet på SELECT * och DDL-ändringar.

Koden som jag kommer att använda i den här artikeln kan köras i vilken databas som helst, men i mina demos kommer jag att använda TSQLV5 – samma exempeldatabas som jag använde i tidigare artiklar. Du kan hitta skriptet som skapar och fyller i TSQLV5 här, och dess ER-diagram här.

Att använda SELECT * i vyns inre fråga är en dålig idé

I slutavsnittet av förra månadens artikel ställde jag en fråga som en tankeställare. Jag förklarade att jag tidigare i serien argumenterade för att använda SELECT * i de inre tabelluttrycken som används med härledda tabeller och CTE:er. Se del 3 i serien för detaljer om du behöver fräscha upp ditt minne. Jag bad dig sedan fundera på om samma rekommendation fortfarande skulle vara giltig för det inre tabelluttrycket som används för att definiera vy. Kanske var rubriken på det här avsnittet redan en spoiler, men jag kan säga att med vyer är det faktiskt en mycket dålig idé.

Jag börjar med vyer som inte är definierade med SCHEMABINDING-attributet, som förhindrar relevanta DDL-ändringar av beroende objekt, och förklarar sedan hur saker förändras när du använder det här attributet.

Jag hoppar direkt till ett exempel eftersom detta kommer att vara det enklaste sättet att presentera mitt argument.

Använd följande kod för att skapa en tabell som heter dbo.T1 och en vy som heter dbo.V1 baserat på en fråga med SELECT * mot tabellen:

ANVÄND TSQLV5; SLÄPP VISNING OM FINNS dbo.V1;SLÄPP TABELL OM FINNS dbo.T1;GO SKAPA TABELL dbo.T1( keycol INT INTE NULL IDENTITETSBEGRÄNSNING PK_T1 PRIMÄRNYCKEL, intcol INT INTE NULL, charcol VARCHAR(10) NOT NULL); INSERT INTO dbo.T1(intcol, charcol) VÄRDEN (10, 'A'), (20, 'B');GO SKAPA ELLER ÄNDRA VISA dbo.V1AS SELECT * FROM dbo.T1;GO

Observera att tabellen för närvarande har kolumnerna keycol, intcol och charcol.

Använd följande kod för att fråga vyn:

VÄLJ * FRÅN dbo.V1;

Du får följande utdata:

keycol intcol charcol----------- ----------- ----------1 10 A2 20 B

Inget speciellt här.

När du skapar en vy registrerar SQL Server metadatainformation i ett antal katalogobjekt. Den registrerar en del allmän information som du kan fråga via sys.views, vydefinitionen som du kan fråga via sys.sql_modules, kolumninformation som du kan fråga via sys.columns och mer information är tillgänglig via andra objekt. Vad som också är relevant för vår diskussion är att SQL Server låter dig styra åtkomstbehörigheter mot vyer. Det jag vill varna dig för när du använder SELECT * i vyns inre tabelluttryck är vad som kan hända när DDL-ändringar tillämpas på underliggande beroende objekt.

Använd följande kod för att skapa en användare som heter user1 och ge användaren behörighet att välja kolumnerna keycol och intcol från vyn, men inte charcol:

SLIPPA ANVÄNDARE OM FINNS användare1; SKAPA ANVÄNDARE användare1 UTAN LOGGA IN; GE VAL PÅ dbo.V1(keycol, intcol) TILL användare1;

Låt oss nu inspektera några av de inspelade metadata som är relaterade till vår syn. Använd följande kod för att returnera posten som representerar vyn från sys.views:

VÄLJ SCHEMA_NAME(schema_id) AS schemaname, name, object_id, type_descFROM sys.viewsWHERE object_id =OBJECT_ID(N'dbo.V1');

Denna kod genererar följande utdata:

schemanamn namn objekt-id typ_desc------------ ----- ---------- ----------dbo V1 130099504 VISA 

Använd följande kod för att hämta vydefinitionen från sys.modules:

VÄLJ definition FRÅN sys.sql_modulesWHERE object_id =OBJECT_ID(N'dbo.V1');

Ett annat alternativ är att använda OBJECT_DEFINITION-funktionen så här:

VÄLJ OBJECT_DEFINITION(OBJECT_ID(N'dbo.V1'));

Du får följande utdata:

SKAPA VY dbo.V1AS SELECT * FROM dbo.T1;

Använd följande kod för att fråga vyns kolumndefinitioner från sys.columns:

VÄLJ namn AS column_name, column_id, TYPE_NAME(system_type_id) AS data_typeFROM sys.columnsWHERE object_id =OBJECT_ID(N'dbo.V1');

Som förväntat får du information om vyns tre kolumner keycol, intcol och charcol:

kolumnnamn kolumn-id datatyp-------------------------------keycol 1 intintcol 2 intcharcol 3 varchar

Observera kolumn-ID:n (ordningspositioner) som är associerade med kolumnerna.

Du kan få liknande information genom att fråga standardinformationsschemavyn INFORMATION_SCHEMA.COLUMNS, som så:

SELECT COLUMN_NAME, ORDINAL_POSITION, DATA_TYPEFROM INFORMATION_SCHEMA.COLUMNSWHERE TABLE_SCHEMA =N'dbo' OCH TABLE_NAME =N'V1';

För att få vyns beroendeinformation (objekt den refererar till), kan du fråga sys.dm_sql_referenced_entities, så här:

SELECT OBJECT_NAME(referenced_id) AS referenced_object, referenced_minor_id, COL_NAME(referenced_id, referenced_minor_id) AS column_nameFROM sys.dm_sql_referenced_entities(N'dbo.V1', N'OBJECT');

Du hittar beroendet på tabellen T1 och på dess tre kolumner:

referenced_object refered_minor_id kolumnnamn------------------------ ------------------ ------- ----T1 0 NULLT1 1 keycolT1 2 intcolT1 3 charcol

Som du säkert kunde gissa är referens_minor_id-värdet för kolumner det kolumn-ID du såg tidigare.

Om du vill få behörigheterna för användare1 mot V1, kan du fråga sys.database_permissions, så här:

VÄLJ OBJECT_NAME(major_id) AS referenced_object, minor_id, COL_NAME(major_id, minor_id) AS column_name, permission_nameFROM sys.database_permissionsWHERE major_id =OBJECT_ID(N'dbo.V1') AND grantee_USER_principal_id(N ='user_principal_id(N ='user_principal_id'); före> 

Den här koden genererar följande utdata, vilket bekräftar att användare1 verkligen har valda behörigheter endast mot keycol och intcol, men inte mot charcol:

referenced_object minor_id column_name behörighetsnamn------------------------ ---------- ------------ -- --------------V1 1 keycol SELECTV1 2 intcol SELECT

Återigen, minor_id-värdet är kolumn-ID som du såg tidigare. Vår användare, användare1, har behörighet till kolumnerna vars ID är 1 och 2.

Kör sedan följande kod för att imitera användare1 och för att försöka fråga alla V1:s kolumner:

EXEKUT SOM ANVÄNDARE =​​N'användare1'; VÄLJ * FRÅN dbo.V1;

Som du kan förvänta dig får du ett behörighetsfel på grund av bristen på behörighet att fråga charcol:

Msg 230, Level 14, State 1, Line 141
SELECT-behörigheten nekades i kolumnen 'charcol' för objektet 'V1', databasen 'TSQLV5', schema 'dbo'.

Försök att fråga bara keycol och intcol:

SELECT keycol, intcol FROM dbo.V1;

Denna gång körs frågan framgångsrikt och genererar följande utdata:

keycol intcol----------- -----------1 102 20

Inga överraskningar än så länge.

Kör följande kod för att återgå till din ursprungliga användare:

ÅTERGÅ;

Låt oss nu tillämpa några strukturella ändringar på den underliggande tabellen dbo.T1. Kör följande kod för att först lägga till två kolumner som heter datecol och binarycol, och sedan för att släppa kolumnen intcol:

ALTER TABLE dbo.T1 ADD datecol DATUM NOT NULL DEFAULT('99991231'), binarycol VARBINARY(3) NOT NULL DEFAULT(0x112233); ALTER TABLE dbo.T1 DROP COLUMN intcol;

SQL Server avvisade inte de strukturella ändringarna i kolumner som vyn refererar till eftersom vyn inte skapades med attributet SCHEMABINDING. Nu till fångsten. Vid det här laget har SQL Server ännu inte uppdaterat vyns metadatainformation i de olika katalogobjekten.

Använd följande kod för att fråga vyn, fortfarande med din ursprungliga användare (inte användare1 ännu):

VÄLJ * FRÅN dbo.V1;

Du får följande utdata:

keycol intcol charcol----------- ---------- ----------1 A 9999-12-312 B 9999-12-31 

Observera att intcol faktiskt returnerar charcols innehåll och charcol returnerar datecols innehåll. Kom ihåg att det inte finns någon intcol i tabellen längre men det finns datecol. Dessutom får du inte tillbaka den nya kolumnen binarycol.

För att försöka ta reda på vad som händer, använd följande kod för att fråga vyns kolumnmetadata:

VÄLJ namn AS column_name, column_id, TYPE_NAME(system_type_id) AS data_typeFROM sys.columnsWHERE object_id =OBJECT_ID(N'dbo.V1');

Denna kod genererar följande utdata:

kolumnnamn kolumn-id datatyp-------------------------------keycol 1 intintcol 2 intcharcol 3 varchar

Som du kan se är vyns metadata fortfarande inte uppdaterad. Du kan se intcol som kolumn ID 2 och charcol som kolumn ID 3. I praktiken existerar inte intcol längre, charcol är tänkt att vara kolumn 2 och datecol är tänkt att vara kolumn 3.

Låt oss kontrollera om det finns någon ändring med behörighetsinformation:

VÄLJ OBJECT_NAME(major_id) AS referenced_object, minor_id, COL_NAME(major_id, minor_id) AS column_name, permission_nameFROM sys.database_permissionsWHERE major_id =OBJECT_ID(N'dbo.V1') AND grantee_USER_principal_id(N ='user_principal_id(N ='user_principal_id'); före> 

Du får följande utdata:

referenced_object minor_id column_name behörighetsnamn------------------------ ---------- ------------ -- --------------V1 1 keycol SELECTV1 2 intcol SELECT

Behörighetsinformation visar att användare1 har behörighet till kolumn 1 och 2 i vyn. Men även om metadata tror att kolumn 2 kallas intcol, är den faktiskt mappad till charcol i T1 i praktiken. Det är farligt eftersom användare1 inte ska ha tillgång till charcol. Tänk om den här kolumnen i verkligheten innehåller känslig information som lösenord.

Låt oss imitera användare1 igen och fråga alla vykolumner:

EXECUTE AS USER ='användare1'; VÄLJ * FRÅN dbo.V1;

Du får ett behörighetsfel som säger att du inte har tillgång till charcol:

Msg 230, Level 14, State 1, Line 211
SELECT-behörigheten nekades i kolumnen 'charcol' för objektet 'V1', databasen 'TSQLV5', schema 'dbo'.

Se dock vad som händer när du uttryckligen ber om keycol och intcol:

SELECT keycol, intcol FROM dbo.V1;

Du får följande utdata:

keycol intcol----------- ----------1 A2 B

Den här frågan lyckas, bara den returnerar innehållet av charcol under intcol. Vår användare, användare1, ska inte ha tillgång till denna information. Hoppsan!

Återgå nu till den ursprungliga användaren genom att köra följande kod:

ÅTERGÅ;

Uppdatera SQL-modul

Du kan tydligt se att det är en dålig idé att använda SELECT * i vyns inre tabelluttryck. Men det är inte bara det. I allmänhet är det en bra idé att uppdatera vyns metadata efter varje DDL-ändring till refererade objekt och kolumner. Du kan göra det med sp_refreshview eller den mer allmänna sp_refreshmodulen, som så:

EXEC sys.sp_refreshsqlmodule N'dbo.V1';

Fråga vyn igen, nu när dess metadata har uppdaterats:

VÄLJ * FRÅN dbo.V1;

Den här gången får du det förväntade resultatet:

keycol charcol datecol binarycol----- ---------- ---------- ----------1 A 9999 -12-31 0x1122332 B 9999-12-31 0x112233

Kolumnen charcol är korrekt namngiven och visar rätt data; du ser inte intcol, och du ser de nya kolumnerna datecol och binarycol.

Fråga vyns kolumnmetadata:

VÄLJ namn AS column_name, column_id, TYPE_NAME(system_type_id) AS data_typeFROM sys.columnsWHERE object_id =OBJECT_ID(N'dbo.V1');

Utdata visar nu korrekt kolumnmetadatainformation:

kolumnnamn kolumn-id datatyp----------------------------keycol 1 intcharcol 2 varchardatecol 3 datebinarycol 4 varbinary 

Fråga användare1s behörigheter mot vyn:

VÄLJ OBJECT_NAME(major_id) AS referenced_object, minor_id, COL_NAME(major_id, minor_id) AS column_name, permission_nameFROM sys.database_permissionsWHERE major_id =OBJECT_ID(N'dbo.V1') AND grantee_USER_principal_id(N ='user_principal_id(N ='user_principal_id'); före> 

Du får följande utdata:

referenced_object minor_id column_name behörighetsnamn------------------------ ---------- ------------ -- --------------V1 1 keycol SELECT

Tillståndsinformationen är nu korrekt. Vår användare, user1, har endast behörighet att välja keycol, och behörighetsinformationen för intcol har tagits bort.

För att vara säker på att allt är bra, låt oss testa detta genom att imitera användare1 och fråga vyn:

EXECUTE AS USER ='användare1'; VÄLJ * FRÅN dbo.V1;

Du får två behörighetsfel på grund av bristen på behörigheter mot datecol och binarycol:

Msg 230, Level 14, State 1, Line 281
SELECT-behörigheten nekades i kolumnen 'datecol' för objektet 'V1', databasen 'TSQLV5', schema 'dbo'.

Msg 230, Level 14, State 1, Line 281
SELECT-behörigheten nekades i kolumnen 'binarycol' för objektet 'V1', databasen 'TSQLV5', schema 'dbo'.

Försök att fråga keycol och intcol:

SELECT keycol, intcol FROM dbo.V1;

Den här gången säger felet korrekt att det inte finns någon kolumn som heter intcol:

Medd. 207, nivå 16, tillstånd 1, linje 279

Ogiltigt kolumnnamn 'intcol'.

Fråga endast intcol:

VÄLJ keycol FRÅN dbo.V1;

Den här frågan körs framgångsrikt och genererar följande utdata:

keycol-----------12

Gå nu tillbaka till din ursprungliga användare genom att köra följande kod:

ÅTERGÅ;

Är det tillräckligt att undvika SELECT * och använda explicita kolumnnamn?

Om du följer en praxis som säger nej SELECT * i vyns inre tabelluttryck, skulle detta vara tillräckligt för att hålla dig borta från problem? Nåväl, låt oss se...

Använd följande kod för att återskapa tabellen och vyn, bara denna gång lista kolumnerna explicit i vyns inre fråga:

SLÄPP VISA OM FINNS dbo.V1;SLÄPP TABELL OM FINNS dbo.T1;GO SKAPA TABELL dbo.T1( keycol INT INTE NULL IDENTITETSBEGRÄNSNING PK_T1 PRIMÄRNYCKEL, intcol INT INTE NULL, charcol VARCHARNULL(10) NOT NULL); INSERT INTO dbo.T1(intcol, charcol) VÄRDEN (10, 'A'), (20, 'B');GO CREATE ELLER ALTER VIEW dbo.V1AS SELECT keycol, intcol, charcol FRÅN dbo.T1;GO

Fråga vyn:

VÄLJ * FRÅN dbo.V1;

Du får följande utdata:

keycol intcol charcol----------- ----------- ----------1 10 A2 20 B

Återigen, ge användare1 behörighet att välja keycol och intcol:

GI VAL PÅ dbo.V1(keycol, intcol) TILL användare1;

Tillämpa sedan samma strukturella ändringar som du gjorde tidigare:

ALTER TABLE dbo.T1 ADD datecol DATUM NOT NULL DEFAULT('99991231'), binarycol VARBINARY(3) NOT NULL DEFAULT(0x112233); ALTER TABLE dbo.T1 DROP COLUMN intcol;

Observera att SQL Server accepterade dessa ändringar, även om vyn har en explicit referens till intcol. Återigen, det beror på att vyn skapades utan alternativet SCHEMABINDING.

Fråga vyn:

VÄLJ * FRÅN dbo.V1;

Vid det här laget genererar SQL Server följande fel:

Msg 207, Level 16, State 1, Procedure V1, Line 5 [Batch Start Line 344]
Ogiltigt kolumnnamn 'intcol'.

Msg 4413, Level 16, State 1, Line 345
Kunde inte använda vyn eller funktionen 'dbo.V1' på grund av bindningsfel.

SQL Server försökte lösa intcol-referensen i vyn och misslyckades naturligtvis.

Men vad händer om din ursprungliga plan var att släppa intcol och senare lägga till den igen? Använd följande kod för att lägga till den igen och fråga sedan vyn:

ALTER TABLE dbo.T1 ADD intcol INT INTE NULL DEFAULT(0); VÄLJ * FRÅN dbo.V1;

Denna kod genererar följande utdata:

keycol intcol charcol----------- ----------- ----------1 0 A2 0 B

Resultatet verkar korrekt.

Vad sägs om att fråga vyn som användare1? Låt oss prova det:

EXEKUT SOM ANVÄNDARE =​​'användare1';VÄLJ * FRÅN dbo.V1;

När du frågar alla kolumner får du det förväntade felet på grund av bristen på behörigheter mot charcol:

Msg 230, Level 14, State 1, Line 367
SELECT-behörigheten nekades i kolumnen 'charcol' för objektet 'V1', databasen 'TSQLV5', schema 'dbo'.

Fråga uttryckligen keycol och intcol:

SELECT keycol, intcol FROM dbo.V1;

Du får följande utdata:

keycol intcol----------- -----------1 02 0

Det verkar som att allt är i sin ordning tack vare det faktum att du inte använde SELECT * i vyns inre fråga, även om du inte uppdaterade vyns metadata. Ändå kan det vara en bra praxis att uppdatera vyns metadata efter DDL-ändringar till refererade objekt och kolumner för att vara på den säkra sidan.

Återgå nu till din ursprungliga användare genom att köra följande kod:

ÅTERGÅ;

SCHEMABINDING

Genom att använda SCHEMABINDING view-attributet kan du bespara dig själv mycket av ovannämnda problem. En av nycklarna för att undvika det problem du såg tidigare är att inte använda SELECT * i vyns inre fråga. Men det finns också frågan om strukturella förändringar mot beroende objekt, som att ta bort refererade kolumner, som fortfarande kan resultera i fel när du frågar vyn. Om du använder vyattributet SCHEMABINDING kommer du inte att tillåtas använda SELECT * i den inre frågan. Dessutom kommer SQL Server att avvisa försök att tillämpa relevanta DDL-ändringar på beroende objekt och kolumner. I relevanta fall menar jag ändringar som att ta bort en refererad tabell eller kolumn. Att lägga till en kolumn i en refererad tabell är uppenbarligen inte ett problem, så SCHEMABINDING förhindrar inte en sådan förändring.

För att demonstrera detta, använd följande kod för att återskapa tabellen och vyn, med SCHEMABINDING i vydefinitionen:

SLÄPP VISA OM FINNS dbo.V1;SLÄPP TABELL OM FINNS dbo.T1;GO SKAPA TABELL dbo.T1( keycol INT INTE NULL IDENTITETSBEGRÄNSNING PK_T1 PRIMÄRNYCKEL, intcol INT INTE NULL, charcol VARCHARNULL(10) NOT NULL); INSERT INTO dbo.T1(intcol, charcol) VÄRDEN (10, 'A'), (20, 'B');GO SKAPA ELLER ÄNDRA VISA dbo.V1 MED SCHEMABINDINGAS SELECT * FROM dbo.T1;GO

Du får ett felmeddelande:

Msg 1054, Level 15, State 6, Procedure V1, Line 5 [Batch Start Line 387]
Syntax '*' är inte tillåten i schemabundna objekt.

När du använder SCHEMABINDING får du inte använda SELECT * i vyns inre tabelluttryck.

Försök att skapa vyn igen, bara den här gången med en explicit kolumnlista:

SKAPA ELLER ÄNDRA VISA dbo.V1 MED SCHEMABINDINGAS SELECT keycol, intcol, charcol FRÅN dbo.T1;GO

Den här gången skapades vyn framgångsrikt.

Ge användare1 behörigheter för keycol och intcol:

GI VAL PÅ dbo.V1(keycol, intcol) TILL användare1;

Försök sedan att tillämpa strukturella ändringar i tabellen. Lägg först till ett par kolumner:

ÄNDRA TABELL dbo.T1 ADD datecol DATUM NOT NULL DEFAULT('99991231'), binarycol VARBINARY(3) NOT NULL DEFAULT(0x112233);

Att lägga till kolumner är inget problem eftersom de inte kan ingå i befintliga schemabundna vyer, så den här koden slutförs framgångsrikt.

Försök att ta bort kolumnen intcol:

ÄNDRA TABELL dbo.T1 SLIPP KOLUMN intcol;

Du får följande felmeddelande:

Msg 5074, Level 16, State 1, Line 418
Objektet 'V1' är beroende av kolumn 'intcol'.

Msg 4922, Level 16, State 9, Line 418
ALTER TABLE DROP COLUMN intcol misslyckades eftersom ett eller flera objekt åtkomst till denna kolumn.

Det är inte tillåtet att ta bort eller ändra refererade kolumner när det finns schemabundna objekt.

Om du fortfarande behöver ta bort intcol måste du först släppa den schemabundna referensvyn, tillämpa ändringen och sedan återskapa vyn och tilldela om behörigheter, så här:

DROP VIEW OM FINNS dbo.V1;GO ALTER TABLE dbo.T1 DROP COLUMN intcol;GO SKAPA ELLER ÄNDRA VISA dbo.V1 MED SCHEMABINDINGAS SELECT keycol, charcol, datecol, binarycol FRÅN dbo.T1;GO GRANT SELECT PÅ dbo. V1(keycol, datecol, binarycol) TO user1;GO

Naturligtvis vid denna tidpunkt finns det inget behov av att uppdatera vydefinitionen, eftersom du skapade den på nytt.

Nu när du har testat klart, kör följande kod för rensning:

SLIPPA VISNING OM FINNS dbo.V1;SLAPP TABELL OM FINNS dbo.T1;SLIPPA ANVÄNDARE OM FINNS användare1;

Sammanfattning

Att använda SELECT * i vyns inre tabelluttryck är en mycket dålig idé. Efter att strukturella ändringar har tillämpats på refererade objekt kan du få felaktiga kolumnnamn och till och med tillåta användare att komma åt data som de inte ska ha tillgång till. Det är en viktig praxis att explicit lista de refererade kolumnnamnen.

Genom att använda SCHEMABINDING i vydefinitionen tvingas du explicit lista kolumnnamn, och relevanta strukturella ändringar av beroende objekt avvisas av SQL Server. Därför kan det verka som att skapa vyer med SCHEMBINDING alltid är en bra idé. Det finns dock en varning med detta alternativ. Som du såg blir det en längre och mer komplicerad process att tillämpa strukturella ändringar på refererade objekt när SCHEMBINDING används. Det kan särskilt vara ett problem i system som måste ha mycket hög tillgänglighet. Föreställ dig att du behöver ändra en kolumn definierad som VARCHAR(50) till VARCHAR(60). Det är inte en tillåten ändring om det finns en vy definierad med SCHEMABINDING som refererar till den här kolumnen. Konsekvenserna av att släppa ett gäng refererande åsikter, som kan refereras av andra åsikter, och så vidare, kan vara problematiska för systemet. Kort sagt, det är inte alltid så trivialt för företag att bara anta en policy som säger att SCHEMABINDING ska användas i alla objekt som stödjer det. Men att anta en policy att inte använda SELECT * i vyernas inre frågor borde vara enklare.

Det finns mycket mer att utforska angående vyer. Fortsättning nästa månad...


  1. selectionArgs i SQLiteQueryBuilder fungerar inte med heltalsvärden i kolumner

  2. Hur man skapar ett navigeringsformulär i Microsoft Access

  3. Finns det några alternativ till ett gåbord för många-till-många föreningar?

  4. Galera Cluster Resources