Lorenzo Benaglia


Storie di un SQLlaro mannaro...
Archivio Posts
Anno 2010

Anno 2009

Anno 2008

Anno 2007

Anno 2006

Anno 2005
Calendario
novembre 2024
lmmgvsd
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678

Queste viste che non si rinfrescano...

Ogni tanto mi capita di rispondere sui ng ad una domanda piuttosto ricorrente che riguarda le viste.
Diversi utenti lamentano il fatto che dopo aver apportato delle modifiche strutturali alle tabelle referenziate dalle loro viste, queste ultime restituiscono valori "sballati", oppure se la query utilizzata nella definizione della vista era la classica SELECT *, non vengono restituite le nuove colonne aggiunte successivamente.

Come mai si verifica questo disallineamento?
Prima di iniziare voglio sottolineare che non si tratta di un bug o una anomalia, ma è previsto dallo standard ANSI SQL 92.
Cerchiamo di analizzare meglio la faccenda.

Il comando TSQL CREATE VIEW permette di definire una tabella virtuale che rappresenta in modo alternativo i dati di una o più tabelle base.
Questa tabella virtuale prende il nome di Vista.
Ma che succede dietro le quinte quando andiamo a definire una vista?
SQL Server andrà a popolare alcune tabelle di sistema con i metadati della vista stessa.
In particolare verrà popolata la tabella sysobjects con il nome della vista, syscolumns con le colonne restituite dalla query, sysdepends con i riferimenti alle tabelle utilizzate nella query e syscomments con l'intero comando CREATE VIEW.
Queste informazioni sono memorizzate staticamente, nel senso che non rifletteranno le eventuali modifiche effettuate sulle tabelle base referenziate dalla vista.
Se si modificano le tabelle base, le viste andranno a restituire risultati sbagliati, le colonne saranno disallineate oppure mancheranno del tutto quelle aggiunte successivamente alla definizione della vista.

Come possiamo ovviare a questo inconveniente?
Possiamo seguire 3 strade:

1) Eliminare le viste influenzate dalla modifica e ricrearle successivamente;
2) Modificare le viste tramite il comando ALTER VIEW;
3) Utilizzare la stored procedure estesa di sistema sp_refreshview.

I primi due metodi richiedono necessariamente di conoscere a priori i metadati delle viste e spesso questa operazione è poco agevole se non impossibile (nel caso il cui la vista sia stata criptata tramite la clausola WITH ENCRYPTION).
La stored procedure estesa di sistema sp_refreshview ci semplifica di molto la vita: è sufficiente eseguirla specificando come argomento il nome della vista che si intende "rinfrescare".

Ho preparato un piccolo esempio che illustra come utilizzare la sp_refreshview:

USE tempdb
GO

/* Definisco la tabella dbo.Students */
CREATE TABLE dbo.Students(
StudentID int NOT NULL IDENTITY PRIMARY KEY,
FirstName varchar(10) NOT NULL,
LastName varchar(10) NOT NULL
)
GO

/* La popolo */
SET NOCOUNT ON
INSERT dbo.Students VALUES('Lorenzo', 'Benaglia')
INSERT dbo.Students VALUES('Luca', 'Bianchi')
INSERT dbo.Students VALUES('Andrea', 'Montanari')
INSERT dbo.Students VALUES('Gianluca', 'Hotz')
SET NOCOUNT OFF
GO

/* Definisco la vista dbo.vw_GetStudentsFirstName */
CREATE VIEW dbo.vw_GetStudentsFirstName 
WITH ENCRYPTION
AS
SELECT FirstName
FROM dbo.Students
GO

/* Definisco la vista dbo.vw_GetStudents */
CREATE VIEW dbo.vw_GetStudents
AS
SELECT *
FROM dbo.Students
GO

/* Vediamo il data type della colonna FirstName 
** della tabella e delle due viste 
*/
SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME = N'FirstName'
AND TABLE_SCHEMA = N'dbo'
AND TABLE_NAME IN(N'Students', N'vw_GetStudentsFirstName', N'vw_GetStudents')
GO

/* Output:

TABLE_NAME                COLUMN_NAME  DATA_TYPE 
------------------------- ------------ ----------
Students                  FirstName    varchar
vw_GetStudents            FirstName    varchar
vw_GetStudentsFirstName   FirstName    varchar

(3 row(s) affected)

*/

/* Modifico il data type della colonna FirstName
** nella tabella dbo.Students
*/
ALTER TABLE dbo.Students
ALTER COLUMN FirstName nvarchar(10) NOT NULL
GO

/* Vediamo il data type della colonna FirstName 
** della tabella e delle due viste 
*/
SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME = N'FirstName'
AND TABLE_SCHEMA = N'dbo'
AND TABLE_NAME IN(N'Students', N'vw_GetStudentsFirstName', N'vw_GetStudents')
GO

/* Output:

TABLE_NAME                COLUMN_NAME  DATA_TYPE 
------------------------- ------------ ----------
Students                  FirstName    nvarchar
vw_GetStudents            FirstName    varchar
vw_GetStudentsFirstName   FirstName    varchar

(3 row(s) affected)

*/

/* Come si può vedere la modifica del data type nella tabella base non è stata
** riflessa nei metadati delle viste.
** OK, diamo loro una bella "rinfrescata" :-D
*/
EXEC sp_refreshview [dbo.vw_GetStudentsFirstName]
EXEC sp_refreshview [dbo.vw_GetStudents]
GO

/* Vediamo il data type della colonna FirstName 
** della tabella e delle due viste 
*/
SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME = N'FirstName'
AND TABLE_SCHEMA = N'dbo'
AND TABLE_NAME IN(N'Students', N'vw_GetStudentsFirstName', N'vw_GetStudents')
GO

/* Output:

TABLE_NAME                COLUMN_NAME  DATA_TYPE
------------------------- ------------ ----------
Students                  FirstName    nvarchar
vw_GetStudents            FirstName    nvarchar
vw_GetStudentsFirstName   FirstName    nvarchar

(3 row(s) affected)

*/

/* Nel caso in cui le viste da aggiornare siano tante, 
** è possibile generare i comandi di aggiornamento 
** utilizzando le information_schema views
*/
SELECT N'EXEC sp_refreshview [' +
TABLE_SCHEMA + N'.' +
TABLE_NAME + N']' AS RefreshCommand
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = N'VIEW'
AND TABLE_NAME NOT LIKE 'sys%'
GO

/* Output:

RefreshCommand
--------------------------------------------------
EXEC sp_refreshview [dbo.vw_GetStudents]
EXEC sp_refreshview [dbo.vw_GetStudentsFirstName]

(2 row(s) affected)

*/

/* Pulizia */
DROP VIEW dbo.vw_GetStudentsFirstName, dbo.vw_GetStudents
DROP TABLE dbo.Students


Teoricamente una volta che è stata definita una vista o una user defined function (udf), non ha senso modificare gli oggetti referenziati da esse (come le tabelle base) dato che alcune modifiche potrebbero causare i malfunzionamenti di cui abbiamo appena discusso.
SQL Server 2000 ci permette di stabilire una sorta di contratto (chiamato bind) mediante l'attributo SCHEMABINDING che lega una vista o una udf agli oggetti referenziati.
Ogni operazione sugli oggetti referenziati che comporterà un potenziale malfunzionamento agli oggetti referenzianti verrà inibito, scatenando una eccezione.
Per capire meglio il funzionamento dell'attributo SCHEMABINDING guardiamo il seguente esempio:

 
USE tempdb
GO

/* Definisco la tabella dbo.Students */
CREATE TABLE dbo.Students(
StudentID int NOT NULL IDENTITY PRIMARY KEY,
FirstName varchar(10) NOT NULL,
LastName varchar(10) NOT NULL
)
GO

/* La popolo */
SET NOCOUNT ON
INSERT dbo.Students VALUES('Lorenzo', 'Benaglia')
INSERT dbo.Students VALUES('Luca', 'Bianchi')
INSERT dbo.Students VALUES('Andrea', 'Montanari')
INSERT dbo.Students VALUES('Gianluca', 'Hotz')
SET NOCOUNT OFF
GO

/* Definisco la vista dbo.vw_GetStudentsFirstName */
CREATE VIEW dbo.vw_GetStudentsFirstName 
WITH SCHEMABINDING
AS
SELECT FirstName
FROM dbo.Students
GO

/* 1) Provo ad eliminare la tabella dbo.Students referenziata dalla vista */
DROP TABLE dbo.Students
GO

/* Output:

Server: Msg 3729, Level 16, State 1, Line 1
Cannot DROP TABLE 'dbo.Students' because it is being referenced by object 'vw_GetStudentsFirstName'.

*/

/* 2) Provo a modificare il data type della colonna FirstName (utilizzato dalla vista) */
ALTER TABLE dbo.Students
ALTER COLUMN FirstName varchar(15) NOT NULL
GO

/* Output:

Server: Msg 5074, Level 16, State 3, Line 1
The object 'vw_GetStudentsFirstName' is dependent on column 'FirstName'.
Server: Msg 4922, Level 16, State 1, Line 1
ALTER TABLE ALTER COLUMN FirstName failed because one or more objects access this column.

*/

/* 2.1) La cosa interessante è che l'eccezione viene scatenata anche se per assurdo
** andiamo a modificare la colonna FirstName specificando esattamente lo stesso data type,
** la stessa lunghezza in bytes e la stessa nullability utilizzate nella definizione della tabella
*/
ALTER TABLE dbo.Students
ALTER COLUMN FirstName varchar(10) NOT NULL
GO

/* Output:

Server: Msg 5074, Level 16, State 3, Line 1
The object 'vw_GetStudentsFirstName' is dependent on column 'FirstName'.
Server: Msg 4922, Level 16, State 1, Line 1
ALTER TABLE ALTER COLUMN FirstName failed because one or more objects access this column.

*/

/* 3) Provo a modificare il data type della colonna LastName (non utilizzata dalla vista) */
ALTER TABLE dbo.Students
ALTER COLUMN LastName varchar(15) NOT NULL
GO

/* Output:

The command(s) completed successfully.

*/

/* Pulizia */
DROP VIEW dbo.vw_GetStudentsFirstName
DROP TABLE dbo.Students


Il mio suggerimento è quello di utilizzare l'attributo SCHEMABINDING una volta che la struttura del database è stata verificata e consolidata, in modo da evitare potenziali malfunzionamente spesso difficili da rintracciare.
Categoria: SQL Server
venerdì, 26 ago 2005 Ore. 00.34
Statistiche
  • Views Home Page: 966.597
  • Views Posts: 715.252
  • Views Gallerie: 13.668.087
  • n° Posts: 300
  • n° Commenti: 314
Mappa





















Copyright © 2002-2007 - Blogs 2.0
dotNetHell.it | Home Page Blogs
ASP.NET 2.0 Windows 2003