0.. 前言
最近發現某個 Table 加上欄位後, 但相依於該 Table 的 View 卻沒有異動的狀況.
本文只取1層的相依物件, 若要取全部相依物件, 請參考 另一篇進階 的文章
茲分為以下幾個部份及實作步驟進行說明:
1.. 問題重現
2.. 解決方式
3.. 結論
4.. 參考文件
本文只取1層的相依物件, 若要取全部相依物件, 請參考 另一篇進階 的文章
茲分為以下幾個部份及實作步驟進行說明:
1.. 問題重現
2.. 解決方式
3.. 結論
4.. 參考文件
1.. 問題重現
(1) 原始狀態
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | -- =========================== -- 建立資料庫 -- =========================== DROP DATABASE IF EXISTS Cookies; GO CREATE DATABASE Cookies; GO -- =========================== -- 切換使用中的資料庫 -- =========================== USE Cookies; GO -- =========================== -- 移除 View / Table -- =========================== DROP VIEW IF EXISTS [dbo].[vwProducts]; GO DROP TABLE IF EXISTS [dbo].[Products]; GO DROP TABLE IF EXISTS [dbo].[Categories] GO |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | -- =========================== -- 建立 Table / View -- =========================== CREATE TABLE [dbo].[Categories] ( [Id] INT NOT NULL , [ Name ] NVARCHAR(30) NOT NULL CONSTRAINT PK_Catgories PRIMARY KEY ( [Id] ) ); GO CREATE TABLE [dbo].[Products] ( [Id] INT NOT NULL , [ Name ] NVARCHAR(30) NOT NULL , [Category] INT NOT NULL , [Price] INT NOT NULL CONSTRAINT PK_Products PRIMARY KEY ( [Id] ) , CONSTRAINT FK_Categories FOREIGN KEY ( [Category] ) REFERENCES [dbo].[Categories] ( [Id] ) ); GO CREATE VIEW [dbo].[vwProducts] AS SELECT P.*, CategoryName = C.[ Name ] FROM [Products] P LEFT JOIN [Categories] C ON (P.[Category] = C.[Id]) ; GO |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | -- =========================== -- 新增測試資料 -- =========================== INSERT INTO [dbo].[Categories] VALUES ( 1, N '餅乾類' ) , ( 2, N '蛋糕類' ) , ( 3, N '奶酪類' ) ; GO INSERT INTO [dbo].[Products] VALUES ( 1, N '夏威夷豆塔' , 1, 50 ) , ( 2, N '堅果塔' , 1, 55 ) , ( 3, N '養生堅果' , 1, 60 ) , ( 4, N '乳酪塔' , 1, 60 ) , ( 5, N '檸檬塔' , 1, 60 ) , ( 6, N '草莓塔' , 1, 65 ) , ( 7, N '輕乳酪蛋糕(片)' , 2, 65 ) , ( 8, N '重乳酪蛋糕(片)' , 2, 70 ) , ( 9, N '抹茶奶酪' , 3, 90 ) , ( 10, N '草莓奶酪' , 3, 85 ) , ( 11, N '芒果奶酪' , 3, 85 ) , ( 12, N '紅豆奶酪' , 3, 80 ) ; GO |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | -- =========================== -- 查詢資料 -- =========================== SELECT * FROM [dbo].[vwProducts] ; GO -- OUTPUT -- Id Name Category Price CategoryName -- 1 夏威夷豆塔 1 50 餅乾類 -- 2 堅果塔 1 55 餅乾類 -- 3 養生堅果 1 60 餅乾類 -- 4 乳酪塔 1 60 餅乾類 -- 5 檸檬塔 1 60 餅乾類 -- 6 草莓塔 1 65 餅乾類 -- 7 輕乳酪蛋糕(片) 2 65 蛋糕類 -- 8 重乳酪蛋糕(片) 2 70 蛋糕類 -- 9 抹茶奶酪 3 90 奶酪類 -- 10 草莓奶酪 3 85 奶酪類 -- 11 芒果奶酪 3 85 奶酪類 -- 12 紅豆奶酪 3 80 奶酪類 |
(2) 新增欄位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | -- =========================== -- 在 TABLE: Products 加上欄位 -- =========================== ALTER TABLE [dbo].[Products] ADD [ValidDay] INT NULL ; GO UPDATE [dbo].[Products] SET [ValidDay] = 14 ; GO ALTER TABLE [dbo].[Products] ALTER COLUMN [ValidDay] INT NOT NULL ; GO |
(3) 查詢後, 發現怪怪的狀況, 有沒有發現, CategoryName 的值, 居然是我們剛剛加入的 [ValidDay]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | -- =========================== -- 再次查詢資料 (怪怪的 ...) -- =========================== SELECT * FROM [dbo].[vwProducts] ; GO -- OUTPUT -- Id Name Category Price CategoryName -- 1 夏威夷豆塔 1 50 14 -- 2 堅果塔 1 55 14 -- 3 養生堅果 1 60 14 -- 4 乳酪塔 1 60 14 -- 5 檸檬塔 1 60 14 -- 6 草莓塔 1 65 14 -- 7 輕乳酪蛋糕(片) 2 65 14 -- 8 重乳酪蛋糕(片) 2 70 14 -- 9 抹茶奶酪 3 90 14 -- 10 草莓奶酪 3 85 14 -- 11 芒果奶酪 3 85 14 -- 12 紅豆奶酪 3 80 14 |
2.. 解決方式
整合參考文件前 2 篇的作法, 並改用 CURSOR 的方式, 應該比較容易理解. 一般而言, 通常會有以下 2 種情境.
(1) 剛改完 Table, 還記得改過的 Table 名稱.
(2) 改了好幾個 Table, 經過一段時間, 已經忘了有那些 Table 有異動過, 但依稀記大概什麼時候開始異動; 或者乾脆全部 Table 都找出來.
以下茲就上述 2 種情境, 列出解決方式, 其實主要重點在於 sp_refreshview 這個 Stored Procedure, 及 sql_expression_dependencies 這個 View; 細節可以參考 Microsoft Docs, 這裡就不多作描述.
(1) 剛改完 Table, 還記得改過的 Table 名稱.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | SET NOCOUNT ON ; DECLARE @ls_tbname NVARCHAR(256) = N 'Products' ; DECLARE @ls_vwname NVARCHAR(256) = N '' ; CREATE TABLE #temp1 ( [ViewName] NVARCHAR(256) ); INSERT INTO #temp1 SELECT DISTINCT A. name FROM sys.objects A INNER JOIN sys.sql_expression_dependencies AS B ON ( A.object_id = B.referencing_id ) WHERE A.type = 'V' AND A. name LIKE 'vw%' -- 額外的篩選條件 AND B.referenced_id = OBJECT_ID(@ls_tbname) ; DECLARE cur_temp1 CURSOR FOR SELECT * FROM #temp1; OPEN cur_temp1; FETCH NEXT FROM cur_temp1 INTO @ls_vwname; WHILE (@@FETCH_STATUS = 0) BEGIN PRINT 'Refreshing --> ' + @ls_vwname; EXEC sp_refreshview @ls_vwname; FETCH NEXT FROM cur_temp1 INTO @ls_vwname; END ; CLOSE cur_temp1; DEALLOCATE cur_temp1; DROP TABLE #temp1; |
(2) 改了好幾個 Table, 經過一段時間, 已經忘了有那些 Table 有異動過, 但依稀記大概什麼時候開始異動; 或者乾脆全部 Table 都找出來.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | SET NOCOUNT ON ; DECLARE @ld_modify DATETIME = '2018-6-14' ; DECLARE @ls_tbname NVARCHAR(256) = N '' ; DECLARE @ls_vwname NVARCHAR(256) = N '' ; CREATE TABLE #temp1 ( [TableName] NVARCHAR(256) , [ViewName] NVARCHAR(256) ); INSERT INTO #temp1 SELECT DISTINCT C. name , A. name FROM sys.objects A INNER JOIN sys.sql_expression_dependencies AS B ON ( A.object_id = B.referencing_id ) INNER JOIN sys.tables AS C ON (B.referenced_id = C.object_id) WHERE A.type = 'V' AND A. name LIKE 'vw%' -- 額外的篩選條件 AND C.modify_date >= @ld_modify ; DECLARE cur_temp1 CURSOR FOR SELECT * FROM #temp1; OPEN cur_temp1; FETCH NEXT FROM cur_temp1 INTO @ls_tbname, @ls_vwname; WHILE (@@FETCH_STATUS = 0) BEGIN PRINT 'Refreshing --> ' + @ls_vwname + '(from ' + @ls_tbname + ')' ; EXEC sp_refreshview @ls_vwname; FETCH NEXT FROM cur_temp1 INTO @ls_tbname, @ls_vwname; END ; CLOSE cur_temp1; DEALLOCATE cur_temp1; DROP TABLE #temp1; |
重新查詢, 可得正確的結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | -- =========================== -- 再次查詢資料 (正常了) -- =========================== SELECT * FROM [dbo].[vwProducts] ; GO -- OUTPUT -- Id Name Category Price ValidDay CategoryName -- 1 夏威夷豆塔 1 50 14 餅乾類 -- 2 堅果塔 1 55 14 餅乾類 -- 3 養生堅果 1 60 14 餅乾類 -- 4 乳酪塔 1 60 14 餅乾類 -- 5 檸檬塔 1 60 14 餅乾類 -- 6 草莓塔 1 65 14 餅乾類 -- 7 輕乳酪蛋糕(片) 2 65 14 蛋糕類 -- 8 重乳酪蛋糕(片) 2 70 14 蛋糕類 -- 9 抹茶奶酪 3 90 14 奶酪類 -- 10 草莓奶酪 3 85 14 奶酪類 -- 11 芒果奶酪 3 85 14 奶酪類 -- 12 紅豆奶酪 3 80 14 奶酪類 |
3.. 結論
如果您的應用系統有用到 View 的話, 當您在增刪 Table 欄位時, 若不確定相依於該 Table 的 View 定義是否含有 SELECT *, 最好重新整理(refresh) View, 以避免發生本文所描述的問題.
或許您會想到, View 的定義裡, 不要用 SELECT * 的方式, 乖乖一個欄位一個欄位 SELECT, 也可以避開該問題; 但這樣 base Table 增刪欄位時, 如果 View 有用到的話, 也要跟著一併增刪欄位, 這就不是 sp_refreshview 所能處理, 而是要手工查 sql_expression_dependencies, 找出相關的 View, 逐一修改.
或許您會想到, View 的定義裡, 不要用 SELECT * 的方式, 乖乖一個欄位一個欄位 SELECT, 也可以避開該問題; 但這樣 base Table 增刪欄位時, 如果 View 有用到的話, 也要跟著一併增刪欄位, 這就不是 sp_refreshview 所能處理, 而是要手工查 sql_expression_dependencies, 找出相關的 View, 逐一修改.
4.. 參考文件
(1) SQL Server Central, Rebuild or recompile all views, Doug Deneau, 2017/10/05
(2) SQL Server Portal, SQL SERVER – How to refresh all views related to modified tables, Muhammad Imran, 2014/06/15
(3) Microsoft Docs, sp_refreshview (Transact-SQL)
(4) Microsoft Docs, sys.sql_expression_dependencies (Transact-SQL)
(5) 黑暗執行緒, 【茶包射手筆記】在 View 使用 SELECT * 的風險, 2017/02/15
沒有留言:
張貼留言