Som en del af serien om tabeludtryk startede jeg i sidste måned dækningen af visninger. Specifikt startede jeg dækningen af logiske aspekter af synspunkter og sammenlignede deres design med afledte tabeller og CTE'er. I denne måned vil jeg fortsætte dækningen af logiske aspekter af synspunkter og fokusere min opmærksomhed på SELECT * og DDL ændringer.
Koden, som jeg vil bruge i denne artikel, kan udføres i enhver database, men i mine demoer vil jeg bruge TSQLV5 - den samme eksempeldatabase, som jeg brugte i tidligere artikler. Du kan finde scriptet, der opretter og udfylder TSQLV5 her, og dets ER-diagram her.
At bruge SELECT * i visningens indre forespørgsel er en dårlig idé
I konklusionen i sidste måneds artikel stillede jeg et spørgsmål som stof til eftertanke. Jeg forklarede, at jeg tidligere i serien argumenterede for at bruge SELECT * i de indre tabeludtryk, der bruges med afledte tabeller og CTE'er. Se del 3 i serien for detaljer, hvis du har brug for at genopfriske din hukommelse. Jeg bad dig derefter overveje, om den samme anbefaling stadig ville være gyldig for det indre tabeludtryk, der bruges til at definere visning. Måske var titlen på dette afsnit allerede en spoiler, men jeg vil sige lige om hjørnet, at med visninger er det faktisk en meget dårlig idé.
Jeg starter med visninger, der ikke er defineret med SCHEMABINDING-attributten, som forhindrer relevante DDL-ændringer til afhængige objekter, og forklarer derefter, hvordan tingene ændrer sig, når du bruger denne attribut.
Jeg springer direkte til et eksempel, da dette vil være den nemmeste måde at præsentere mit argument på.
Brug følgende kode til at oprette en tabel kaldet dbo.T1 og en visning kaldet dbo.V1 baseret på en forespørgsel med SELECT * mod tabellen:
BRUG TSQLV5; DROP VISNING HVIS FINDER dbo.V1;DROP TABEL HVIS FINDER dbo.T1;GO OPRET TABEL dbo.T1( keycol INT IKKE NULL IDENTITETSBEGRÆNSNING PK_T1 PRIMÆR NØGLE, intcol INT IKKE NULL, charcol VARCHAR(10) NOT NULL); INSERT INTO dbo.T1(intcol, charcol) VALUES (10, 'A'), (20, 'B');GO CREATE OR ALTER VIEW dbo.V1AS SELECT * FROM dbo.T1;GO
Bemærk, at tabellen i øjeblikket har kolonnerne keycol, intcol og charcol.
Brug følgende kode til at forespørge på visningen:
VÆLG * FRA dbo.V1;
Du får følgende output:
keycol intcol charcol----------- ----------- ----------1 10 A2 20 B
Ikke noget særligt her.
Når du opretter en visning, registrerer SQL Server metadataoplysninger i en række katalogobjekter. Det registrerer nogle generelle oplysninger, som du kan forespørge på via sys.views, visningsdefinitionen, som du kan forespørge på via sys.sql_modules, kolonneinformation, som du kan forespørge via sys.columns, og mere information er tilgængelig via andre objekter. Hvad der også er relevant for vores diskussion er, at SQL Server lader dig kontrollere adgangstilladelser mod visninger. Det, jeg vil advare dig om, når du bruger SELECT * i visningens indre tabeludtryk, er, hvad der kan ske, når DDL-ændringer anvendes på underliggende afhængige objekter.
Brug følgende kode til at oprette en bruger kaldet bruger1 og giv brugeren tilladelse til at vælge kolonnerne keycol og intcol fra visningen, men ikke charcol:
DROP BRUGER HVIS FINNES bruger1; OPRET BRUGER bruger1 UDEN LOGIN; TILDEL VALG PÅ dbo.V1(keycol, intcol) TIL bruger1;
Lad os på dette tidspunkt inspicere nogle af de registrerede metadata relateret til vores synspunkt. Brug følgende kode til at returnere den post, der repræsenterer visningen fra sys.views:
VÆLG SCHEMA_NAME(skema_id) SOM skemanavn, navn, objekt-id, type_descFROM sys.viewsWHERE object_id =OBJECT_ID(N'dbo.V1');
Denne kode genererer følgende output:
skemanavn navn objekt_id type_desc---------------- ----- ------------------ ----------dbo V1 130099504 VISNINGBrug følgende kode for at få visningsdefinitionen fra sys.modules:
VÆLG definition FRA sys.sql_modulesWHERE object_id =OBJECT_ID(N'dbo.V1');En anden mulighed er at bruge OBJECT_DEFINITION-funktionen som sådan:
VÆLG OBJECT_DEFINITION(OBJECT_ID(N'dbo.V1'));Du får følgende output:
OPRET VISNING dbo.V1AS SELECT * FRA dbo.T1;Brug følgende kode til at forespørge visningens kolonnedefinitioner fra sys.columns:
VÆLG navn AS column_name, column_id, TYPE_NAME(system_type_id) AS data_typeFROM sys.columnsWHERE object_id =OBJECT_ID(N'dbo.V1');Som forventet får du information om visningens tre kolonner keycol, intcol og charcol:
column_name column_id data_type------ ------------------ ----------keycol 1 intintcol 2 intcharcol 3 varcharObserver de kolonne-id'er (ordinære positioner), der er knyttet til kolonnerne.
Du kan få lignende oplysninger ved at forespørge på standardinformationsskemavisningen INFORMATION_SCHEMA.COLUMNS, som sådan:
SELECT COLUMN_NAME, ORDINAL_POSITION, DATA_TYPEFROM INFORMATION_SCHEMA.COLUMNSWHERE TABLE_SCHEMA =N'dbo' OG TABLE_NAME =N'V1';For at få visningens afhængighedsoplysninger (objekter, den refererer til), kan du forespørge sys.dm_sql_referenced_entities, som sådan:
VÆLG 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 finder afhængigheden på tabellen T1 og på dens tre kolonner:
referenced_object referenced_minor_id column_name------------------------ ------------------ ------- ----T1 0 NULLT1 1 keycolT1 2 intcolT1 3 charcolSom du sikkert kunne gætte, er reference_minor_id-værdien for kolonner det kolonne-id, du så tidligere.
Hvis du ønsker at få tilladelserne for bruger1 mod V1, kan du forespørge sys.database_permissions, som sådan:
VÆLG 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_PRINCIPAL_ID(N ='user_principal_id'); før>Denne kode genererer følgende output, der bekræfter, at bruger1 faktisk kun har udvalgte tilladelser mod keycol og intcol, men ikke mod charcol:
referenced_object minor_id column_name permission_name------------------------ ------------------ ------------ -- --------------V1 1 keycol SELECTV1 2 intcol SELECTIgen er minor_id-værdien det kolonne-id, du så tidligere. Vores bruger, bruger1, har tilladelser til de kolonner, hvis id er 1 og 2.
Kør derefter følgende kode for at efterligne bruger1 og for at prøve at forespørge alle V1s kolonner:
UDFØR SOM BRUGER =N'bruger1'; VÆLG * FRA dbo.V1;Som du ville forvente, får du en tilladelsesfejl på grund af manglende tilladelse til at forespørge charcol:
Msg 230, Level 14, State 1, Line 141
SELECT-tilladelsen blev nægtet på kolonnen 'charcol' for objektet 'V1', databasen 'TSQLV5', skemaet 'dbo'.Prøv kun at forespørge keycol og intcol:
SELECT keycol, intcol FROM dbo.V1;Denne gang kører forespørgslen med succes og genererer følgende output:
keycol intcol----------- -----------1 102 20Ingen overraskelser indtil videre.
Kør følgende kode for at vende tilbage til din oprindelige bruger:
REVERT;Lad os nu anvende et par strukturelle ændringer på den underliggende tabel dbo.T1. Kør følgende kode for først at tilføje to kolonner kaldet datecol og binarycol, og derefter for at slippe kolonnen intcol:
ÆNDRINGSTABEL dbo.T1 ADD datecol DATO NOT NULL DEFAULT('99991231'), binarycol VARBINARY(3) NOT NULL DEFAULT(0x112233); ÆNDRINGSTABEL dbo.T1 DROP KOLONNE intcol;SQL Server afviste ikke de strukturelle ændringer af kolonner, der refereres til af visningen, da visningen ikke blev oprettet med SCHEMABINDING-attributten. Nu til fangsten. På dette tidspunkt har SQL Server endnu ikke opdateret visningens metadataoplysninger i de forskellige katalogobjekter.
Brug følgende kode til at forespørge på visningen, stadig med din oprindelige bruger (ikke bruger1 endnu):
VÆLG * FRA dbo.V1;Du får følgende output:
keycol intcol charcol----------- ---------- ----------1 A 9999-12-312 B 9999-12-31Bemærk, at intcol faktisk returnerer charcols indhold, og charcol returnerer datecols indhold. Husk, der er ingen intcol i tabellen længere, men der er datecol. Du får heller ikke den nye kolonne binarycol tilbage.
For at prøve at finde ud af, hvad der foregår, skal du bruge følgende kode til at forespørge visningens kolonnemetadata:
VÆLG navn AS column_name, column_id, TYPE_NAME(system_type_id) AS data_typeFROM sys.columnsWHERE object_id =OBJECT_ID(N'dbo.V1');Denne kode genererer følgende output:
column_name column_id data_type------ ------------------ ----------keycol 1 intintcol 2 intcharcol 3 varcharSom du kan se, er visningens metadata stadig ikke opdateret. Du kan se intcol som kolonne ID 2 og charcol som kolonne ID 3. I praksis eksisterer intcol ikke længere, charcol skulle være kolonne 2, og datecol skulle være kolonne 3.
Lad os tjekke, om der er nogen ændring med tilladelsesoplysninger:
VÆLG 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_PRINCIPAL_ID(N ='user_principal_id'); før>Du får følgende output:
referenced_object minor_id column_name permission_name------------------------ ------------------ ------------ -- --------------V1 1 keycol SELECTV1 2 intcol SELECTTilladelsesoplysninger viser, at bruger1 har tilladelser til kolonne 1 og 2 i visningen. Men selvom metadata tror, at kolonne 2 hedder intcol, er den faktisk afbildet til charcol i T1 i praksis. Det er farligt, da bruger1 ikke formodes at have adgang til charcol. Hvad hvis denne kolonne i det virkelige liv indeholder følsomme oplysninger som adgangskoder.
Lad os efterligne bruger1 igen og forespørge alle visningskolonner:
EXECUTE AS USER ='bruger1'; VÆLG * FRA dbo.V1;Du får en tilladelsesfejl, der siger, at du ikke har adgang til charcol:
Msg 230, Level 14, State 1, Line 211
SELECT-tilladelsen blev nægtet på kolonnen 'charcol' for objektet 'V1', databasen 'TSQLV5', skemaet 'dbo'.Se dog, hvad der sker, når du eksplicit beder om keycol og intcol:
SELECT keycol, intcol FROM dbo.V1;Du får følgende output:
keycol intcol----------- ----------1 A2 BDenne forespørgsel lykkes, kun den returnerer indholdet af charcol under intcol. Vores bruger, bruger1, formodes ikke at have adgang til disse oplysninger. Ups!
På dette tidspunkt skal du vende tilbage til den oprindelige bruger ved at køre følgende kode:
REVERT;Opdater SQL-modul
Du kan tydeligt se, at det er en dårlig idé at bruge SELECT * i visningens indre tabeludtryk. Men det er ikke kun det. Generelt er det en god idé at opdatere visningens metadata efter hver DDL-ændring til refererede objekter og kolonner. Du kan gøre det ved at bruge sp_refreshview eller det mere generelle sp_refreshmodul, som sådan:
EXEC sys.sp_refreshsqlmodule N'dbo.V1';Forespørg visningen igen, nu hvor dens metadata er blevet opdateret:
VÆLG * FRA dbo.V1;Denne gang får du det forventede output:
keycol charcol datecol binarycol--------- ---------- ---------- ----------1 A 9999 -12-31 0x1122332 B 9999-12-31 0x112233Kolonnen charcol er navngivet korrekt og viser de korrekte data; du kan ikke se intcol, og du ser de nye kolonner datecol og binarycol.
Forespørg visningens kolonnemetadata:
VÆLG navn AS column_name, column_id, TYPE_NAME(system_type_id) AS data_typeFROM sys.columnsWHERE object_id =OBJECT_ID(N'dbo.V1');Outputtet viser nu korrekte kolonne-metadataoplysninger:
column_name column_id data_type----------------------- ----------keycol 1 intcharcol 2 varchardatecol 3 datebinarycol 4 varbinaryForespørg bruger1s tilladelser mod visningen:
VÆLG 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_PRINCIPAL_ID(N ='user_principal_id'); før>Du får følgende output:
referenced_object minor_id column_name permission_name------------------------ ------------------ ------------ -- --------------V1 1 keycol SELECTTilladelsesoplysningerne er nu korrekte. Vores bruger, user1, har kun tilladelser til at vælge keycol, og tilladelsesoplysninger for intcol er blevet fjernet.
For at være sikker på at alt er i orden, lad os teste dette ved at efterligne bruger1 og forespørge på visningen:
EXECUTE AS USER ='bruger1'; VÆLG * FRA dbo.V1;Du får to tilladelsesfejl på grund af manglen på tilladelser mod datecol og binarycol:
Msg 230, Level 14, State 1, Line 281
SELECT-tilladelsen blev nægtet i kolonnen 'datecol' for objektet 'V1', databasen 'TSQLV5', skemaet 'dbo'.Msg 230, Level 14, State 1, Line 281
SELECT-tilladelsen blev nægtet i kolonnen 'binarycol' for objektet 'V1', databasen 'TSQLV5', skemaet 'dbo'.Prøv at forespørge keycol og intcol:
SELECT keycol, intcol FROM dbo.V1;Denne gang siger fejlen korrekt, at der ikke er nogen kolonne kaldet intcol:
Besked 207, niveau 16, tilstand 1, linje 279Ugyldigt kolonnenavn 'intcol'.
Forespørg kun intcol:
SELECT keycol FROM dbo.V1;Denne forespørgsel kører med succes og genererer følgende output:
keycol-----------12På dette tidspunkt vende tilbage til din oprindelige bruger ved at køre følgende kode:
REVERT;Er det nok at undgå SELECT * og bruge eksplicitte kolonnenavne?
Hvis du følger en praksis, der siger nej SELECT * i visningens indre tabeludtryk, ville det så være nok til at holde dig ude af problemer? Nå, lad os se...
Brug følgende kode til at genskabe tabellen og visningen, kun denne gang angiver kolonnerne eksplicit i visningens indre forespørgsel:
DROP VISNING HVIS FINDER dbo.V1;DROP TABEL HVIS FINDER dbo.T1;GO OPRET TABEL dbo.T1( keycol INT IKKE NULL IDENTITETSBEGRÆNSNING PK_T1 PRIMÆR NØGLE, intcol INT IKKE NULL, charcol VARCHARNULL(10) NOT NULL); INSERT INTO dbo.T1(intcol, charcol) VALUES (10, 'A'), (20, 'B');GO CREATE OR ALTER VIEW dbo.V1AS SELECT keycol, intcol, charcol FRA dbo.T1;GOSpørg visningen:
VÆLG * FRA dbo.V1;Du får følgende output:
keycol intcol charcol----------- ----------- ----------1 10 A2 20 BGiv igen bruger1 tilladelser til at vælge keycol og intcol:
GIV VALG PÅ dbo.V1(keycol, intcol) TIL bruger1;Anvend derefter de samme strukturelle ændringer, som du gjorde før:
ÆNDRINGSTABEL dbo.T1 ADD datecol DATO NOT NULL DEFAULT('99991231'), binarycol VARBINARY(3) NOT NULL DEFAULT(0x112233); ÆNDRINGSTABEL dbo.T1 DROP KOLONNE intcol;Bemærk, at SQL Server accepterede disse ændringer, selvom visningen har en eksplicit reference til intcol. Igen, det er fordi visningen blev oprettet uden muligheden SCHEMABINDING.
Spørg visningen:
VÆLG * FRA dbo.V1;På dette tidspunkt genererer SQL Server følgende fejl:
Msg 207, Level 16, State 1, Procedure V1, Line 5 [Batch Start Line 344]
Ugyldigt kolonnenavn 'intcol'.Msg 4413, Level 16, State 1, Line 345
Kunne ikke bruge view eller funktion 'dbo.V1' på grund af bindingsfejl.SQL Server forsøgte at løse intcol-referencen i visningen, og det lykkedes selvfølgelig ikke.
Men hvad nu hvis din oprindelige plan var at droppe intcol og senere tilføje det igen? Brug følgende kode til at tilføje den igen, og forespørg derefter på visningen:
ÆNDRINGSTABEL dbo.T1 TILFØJ intcol INT IKKE NULL DEFAULT(0); VÆLG * FRA dbo.V1;Denne kode genererer følgende output:
keycol intcol charcol----------- ----------- ----------1 0 A2 0 BResultatet virker korrekt.
Hvad med at forespørge på visningen som bruger1? Lad os prøve det:
EXECUTE AS USER ='bruger1';VÆLG * FRA dbo.V1;Når du forespørger alle kolonner, får du den forventede fejl på grund af manglende tilladelser mod charcol:
Msg 230, Level 14, State 1, Line 367
SELECT-tilladelsen blev nægtet på kolonnen 'charcol' for objektet 'V1', databasen 'TSQLV5', skemaet 'dbo'.Forespørg eksplicit på keycol og intcol:
SELECT keycol, intcol FROM dbo.V1;Du får følgende output:
keycol intcol----------- -----------1 02 0Det ser ud til, at alt er i orden takket være det faktum, at du ikke brugte SELECT * i visningens indre forespørgsel, selvom du ikke har opdateret visningens metadata. Alligevel kunne det være en god praksis at opdatere visningens metadata efter DDL-ændringer til refererede objekter og kolonner for at være på den sikre side.
På dette tidspunkt skal du vende tilbage til din oprindelige bruger ved at køre følgende kode:
REVERT;SKEMABINDING
Ved at bruge SCHEMABINDING view-attributten kan du spare dig selv for en masse af de førnævnte problemer. En af nøglerne til at undgå de problemer, du så tidligere, er ikke at bruge SELECT * i visningens indre forespørgsel. Men der er også spørgsmålet om strukturelle ændringer i forhold til afhængige objekter, som at droppe refererede kolonner, der stadig kan resultere i fejl, når du forespørger i visningen. Ved at bruge SCHEMABINDING view-attributten får du ikke tilladelse til at bruge SELECT * i den indre forespørgsel. Desuden vil SQL Server afvise forsøg på at anvende relevante DDL-ændringer på afhængige objekter og kolonner. I relevant mener jeg ændringer som at droppe en refereret tabel eller kolonne. Tilføjelse af en kolonne til en refereret tabel er naturligvis ikke et problem, så SCHEMABINDING forhindrer ikke en sådan ændring.
For at demonstrere dette skal du bruge følgende kode til at genskabe tabellen og visningen med SCHEMABINDING i visningsdefinitionen:
DROP VISNING HVIS FINDER dbo.V1;DROP TABEL HVIS FINDER dbo.T1;GO OPRET TABEL dbo.T1( keycol INT IKKE NULL IDENTITETSBEGRÆNSNING PK_T1 PRIMÆR NØGLE, intcol INT IKKE NULL, charcol VARCHARNULL(10) NOT NULL); INSERT INTO dbo.T1(intcol, charcol) VALUES (10, 'A'), (20, 'B');GO OPRET ELLER ÆNDRING VIS dbo.V1 MED SCHEMABINDINGAS SELECT * FRA dbo.T1;GODu får en fejlmeddelelse:
Msg 1054, Level 15, State 6, Procedure V1, Line 5 [Batch Start Line 387]
Syntaks '*' er ikke tilladt i skemabundne objekter.Når du bruger SCHEMABINDING, må du ikke bruge SELECT * i visningens indre tabeludtryk.
Prøv at oprette visningen igen, kun denne gang med en eksplicit kolonneliste:
OPRET ELLER ÆNDRING VISNING dbo.V1 MED SCHEMABINDINGAS SELECT keycol, intcol, charcol FRA dbo.T1;GODenne gang er visningen oprettet.
Giv bruger1 tilladelser til keycol og intcol:
GIV VALG PÅ dbo.V1(keycol, intcol) TIL bruger1;Forsøg derefter at anvende strukturelle ændringer på tabellen. Tilføj først et par kolonner:
ÆNDRINGSTABEL dbo.T1 ADD datecol DATO NOT NULL DEFAULT('99991231'), binarycol VARBINARY(3) NOT NULL DEFAULT(0x112233);Tilføjelse af kolonner er ikke et problem, fordi de ikke kan være en del af eksisterende skemabundne visninger, så denne kode fuldføres med succes.
Forsøg på at droppe kolonnen intcol:
ALTER TABLE dbo.T1 DROP COLUMN intcol;Du får følgende fejlmeddelelse:
Msg 5074, Level 16, State 1, Line 418
Objektet 'V1' er afhængig af kolonnen 'intcol'.Msg 4922, Level 16, State 9, Line 418
ALTER TABLE DROP COLUMN intcol mislykkedes, fordi et eller flere objekter får adgang til denne kolonne.Sletning eller ændring af refererede kolonner er ikke tilladt, når der findes skemabundne objekter.
Hvis du stadig har brug for at droppe intcol, skal du først droppe den skemabundne referencevisning, anvende ændringen og derefter genskabe visningen og gentildele tilladelser, som sådan:
DROP VISNING HVIS FINDER dbo.V1;GO ALTER TABLE dbo.T1 DROP COLUMN intcol;GO OPRET ELLER ÆNDRING VIS dbo.V1 MED SCHEMABINDINGAS SELECT keycol, charcol, datecol, binarycol FRA dbo.T1;GO GRANT SELECTON dbo. V1(keycol, datecol, binarycol) TO user1;GONaturligvis på dette tidspunkt er der ingen grund til at opdatere visningsdefinitionen, fordi du har oprettet den på ny.
Nu hvor du er færdig med at teste, skal du køre følgende kode til oprydning:
DROP VISNING HVIS FINDER dbo.V1;DROP TABEL HVIS FINDER dbo.T1;DROP BRUGER HVIS FINDER bruger1;Oversigt
At bruge SELECT * i visningens indre tabeludtryk er en meget dårlig idé. Efter at strukturelle ændringer er blevet anvendt på refererede objekter, kan du få forkerte kolonnenavne og endda tillade brugere at få adgang til data, som de ikke formodes at have adgang til. Det er en vigtig praksis eksplicit at angive de refererede kolonnenavne.
Ved at bruge SCHEMABINDING i visningsdefinitionen er du tvunget til eksplicit at angive kolonnenavne, og relevante strukturelle ændringer af afhængige objekter afvises af SQL Server. Derfor kan det virke som om det altid er en god idé at skabe visninger med SCHEMBINDING. Der er dog en advarsel med denne mulighed. Som du så, bliver det en længere og mere omfattende proces at anvende strukturelle ændringer på refererede objekter, når SCHEMBINDING bruges. Det kan især være et problem i systemer, der skal have meget høj tilgængelighed. Forestil dig, at du skal ændre en kolonne defineret som VARCHAR(50) til VARCHAR(60). Det er ikke en tilladt ændring, hvis der er en visning defineret med SCHEMABINDING, der refererer til denne kolonne. Implikationerne af at droppe en masse refererende synspunkter, som kunne refereres af andre synspunkter og så videre, kunne være problematiske for systemet. Kort sagt, det er ikke altid så trivielt for virksomheder bare at vedtage en politik, der siger, at SCHEMABINDING skal bruges i alle objekter, der understøtter det. Det burde dog være mere ligetil at vedtage en politik om ikke at bruge SELECT * i visningernes indre forespørgsler.
Der er meget mere at udforske med hensyn til udsigter. Fortsættes næste måned...