0.. 前言
1.. 問題重現
2.. 解決方式
3.. 延伸閱讀 (2018.06.25 補充)
4.. 結論
5.. 參考文件
1.. 問題重現
(1) 呈現前一篇文章的最後狀況
-- =========================== -- 切換使用中的資料庫 -- =========================== USE Cookies; GO
-- =========================== -- 前一篇文章的最後狀況 -- =========================== 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 奶酪類
(2) 新增2個 View
-- =========================== -- 移除 View -- =========================== DROP VIEW IF EXISTS [dbo].[vwProductsCookies]; GO DROP VIEW IF EXISTS [dbo].[vwProductsCake]; GO
-- =========================== -- 建立 View -- =========================== CREATE VIEW [dbo].[vwProductsCookies] AS SELECT V.* FROM [vwProducts] V WHERE V.Category = 1 ; GO CREATE VIEW [dbo].[vwProductsCake] AS SELECT V.* FROM [vwProducts] V WHERE V.Category = 2 ; GO
-- =========================== -- 查詢一下 -- =========================== SELECT * FROM vwProductsCookies ; -- 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 餅乾類 SELECT * FROM vwProductsCake ; -- Id Name Category Price ValidDay CategoryName -- 7 輕乳酪蛋糕(片) 2 65 14 蛋糕類 -- 8 重乳酪蛋糕(片) 2 70 14 蛋糕類
(3) 在 table:Products 加入欄位
-- =========================== -- 在 TABLE: Products 加上欄位 -- =========================== -- 加入 宅配方式 : 1-常溫 2-冷藏 3-冷凍 ALTER TABLE [dbo].[Products] ADD [ShipMethod] INT NULL ; GO UPDATE [dbo].[Products] SET [ShipMethod] = 1 WHERE [Category] = 1 ; GO UPDATE [dbo].[Products] SET [ShipMethod] = 2 WHERE [Category] = 2 ; GO UPDATE [dbo].[Products] SET [ShipMethod] = 3 WHERE [Category] = 3 ; GO ALTER TABLE [dbo].[Products] ALTER COLUMN [ShipMethod] INT NOT NULL ; GO
(4) 當然, 此時 SELECT vwProducts, vwProductsCookies, vwProductsCake 都會出現前一篇所述的狀況; CategoryName 的值, 變成新加入的 ShipMethod
-- =========================== -- 查詢一下 -- =========================== SELECT * FROM vwProducts ; -- OUTPUT -- Id Name Category Price ValidDay CategoryName -- 1 夏威夷豆塔 1 50 14 1 -- 2 堅果塔 1 55 14 1 -- 3 養生堅果 1 60 14 1 -- 4 乳酪塔 1 60 14 1 -- 5 檸檬塔 1 60 14 1 -- 6 草莓塔 1 65 14 1 -- 7 輕乳酪蛋糕(片) 2 65 14 2 -- 8 重乳酪蛋糕(片) 2 70 14 2 -- 9 抹茶奶酪 3 90 14 3 -- 10 草莓奶酪 3 85 14 3 -- 11 芒果奶酪 3 85 14 3 -- 12 紅豆奶酪 3 80 14 3 SELECT * FROM vwProductsCookies ; -- OUTPUT -- Id Name Category Price ValidDay CategoryName -- 1 夏威夷豆塔 1 50 14 1 -- 2 堅果塔 1 55 14 1 -- 3 養生堅果 1 60 14 1 -- 4 乳酪塔 1 60 14 1 -- 5 檸檬塔 1 60 14 1 -- 6 草莓塔 1 65 14 1 SELECT * FROM vwProductsCake ; -- Id Name Category Price ValidDay CategoryName -- 7 輕乳酪蛋糕(片) 2 65 14 2 -- 8 重乳酪蛋糕(片) 2 70 14 2
(5) 如今, 我們的狀況, 不再是只有1層相依, 即 vwProducts 相依於 table:Products; 而是以下這種方式:
level 0: table:Products -->
level 1: view: vwProducts -->
level 2: view: vwProductsCookies / view: vwProductsCake
因此, 我們要找另一個方式, 來處理這種多重的相依性
level 0: table:Products -->
level 1: view: vwProducts -->
level 2: view: vwProductsCookies / view: vwProductsCake
因此, 我們要找另一個方式, 來處理這種多重的相依性
2.. 解決方式
(1) 以下提供一個利用 CTE (Common Table Expression) 實作 遞迴 (Recursive) 的方式, 可以把不論直接或間接相依於某個 table 的 view 會部找出來. 之後再逐一進行 refresh VIEW
-- =========================== -- 建立自訂函式, 以取回相依於某的 Table 的所有 View -- sys.sql_expression_dependencies 中的 referencing_xxx 為 child; referenced_xxx 為 parent -- =========================== CREATE FUNCTION [dbo].[ufn_GetDependencyByTable] ( @pi_tbname NVARCHAR(256) = N'' ) RETURNS @result TABLE ( obj_id INT , obj_name NVARCHAR(256) , obj_type VARCHAR(2) , obj_parent NVARCHAR(256) ) AS BEGIN DECLARE @ls_tbname NVARCHAR(256) = @pi_tbname; DECLARE @temp1 TABLE ( [obj_id] INT , [obj_name] NVARCHAR(256) , [obj_type] VARCHAR(2) , [obj_parent] NVARCHAR(256) ) ; -- TABLE NAME INSERT INTO @temp1 SELECT obj_id = A.object_id, obj_name = A.name, obj_type = 'U', obj_parent = '' FROM sys.objects A WHERE A.object_id = OBJECT_ID(@ls_tbname) ; WITH Views AS ( --initialization SELECT obj_id = A.object_id, obj_name = A.name, obj_type = 'V', obj_parent = ( SELECT X.name FROM sys.objects X WHERE X.object_id = B.referenced_id ) FROM sys.objects A INNER JOIN sys.sql_expression_dependencies AS B ON ( A.object_id = B.referencing_id ) WHERE A.type = 'V' AND B.referenced_id = OBJECT_ID(@ls_tbname) UNION ALL --recursive execution SELECT obj_id = A.object_id, obj_name = A.name, obj_type = 'V', obj_parent = ( SELECT X.name FROM sys.objects X WHERE X.object_id = B.referenced_id ) FROM sys.objects A INNER JOIN sys.sql_expression_dependencies AS B ON ( A.object_id = B.referencing_id ) INNER JOIN Views V ON ( B.referenced_id = V.obj_id ) ) INSERT INTO @temp1 SELECT * FROM Views ; INSERT INTO @result ( obj_id , obj_name , obj_type , obj_parent ) SELECT * FROM @temp1; RETURN; END GO
-- =========================== -- 執行 ufn_GetDependencyByTable -- =========================== SELECT * FROM [dbo].[ufn_GetDependencyByTable]('Products') ; -- OUTPUT -- obj_id obj_name obj_type obj_parent -- 597577167 Products U -- 645577338 vwProducts V Products -- 885578193 vwProductsCookies V vwProducts -- 901578250 vwProductsCake V vwProducts
(2) 有沒有發現, 相依於 table:Products 的全部 VIEW, 都被列出來了
(3) 結合上述 REFRESH VIEW 的 script, 及 ufn_GetDependencyByTable() 自訂函式, 我們可以把所有相依於 table:Products 的 VIEW 一次作 refresh.
-- =========================== -- REFRESH VIEW -- =========================== 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 A.obj_name FROM ufn_GetDependencyByTable('Products') A WHERE A.obj_type = 'V' ; 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; -- OUTPUT -- Refreshing --> vwProducts -- Refreshing --> vwProductsCookies -- Refreshing --> vwProductsCake
(4) 看來, 每個 View 都有作 refresh 了; 我們來檢視一下查詢的結果.
-- =========================== -- 檢查一下 -- =========================== SELECT * FROM vwProducts ; -- OUTPUT -- Id Name Category Price ValidDay ShipMethod CategoryName -- 1 夏威夷豆塔 1 50 14 1 餅乾類 -- 2 堅果塔 1 55 14 1 餅乾類 -- 3 養生堅果 1 60 14 1 餅乾類 -- 4 乳酪塔 1 60 14 1 餅乾類 -- 5 檸檬塔 1 60 14 1 餅乾類 -- 6 草莓塔 1 65 14 1 餅乾類 -- 7 輕乳酪蛋糕(片) 2 65 14 2 蛋糕類 -- 8 重乳酪蛋糕(片) 2 70 14 2 蛋糕類 -- 9 抹茶奶酪 3 90 14 3 奶酪類 -- 10 草莓奶酪 3 85 14 3 奶酪類 -- 11 芒果奶酪 3 85 14 3 奶酪類 -- 12 紅豆奶酪 3 80 14 3 奶酪類 SELECT * FROM vwProductsCookies ; -- OUTPUT -- Id Name Category Price ValidDay ShipMethod CategoryName -- 1 夏威夷豆塔 1 50 14 1 餅乾類 -- 2 堅果塔 1 55 14 1 餅乾類 -- 3 養生堅果 1 60 14 1 餅乾類 -- 4 乳酪塔 1 60 14 1 餅乾類 -- 5 檸檬塔 1 60 14 1 餅乾類 -- 6 草莓塔 1 65 14 1 餅乾類 SELECT * FROM vwProductsCake ; -- 7 輕乳酪蛋糕(片) 2 65 14 2 蛋糕類 -- 8 重乳酪蛋糕(片) 2 70 14 2 蛋糕類
(5) 至此, 一切都正常了.
3.. 延伸閱讀 (2018.06.25 補充)
進一步思考, 如何取出全部 Table / View 相關的 View 呢? 可以想像一下, 應該可以由 sys.tables 去 CROSS APPLY ufn_GetDependencyByTable()
為了驗證結果, 先再加一個 View.
為了驗證結果, 先再加一個 View.
-- =========================== -- 增加1個 View -- =========================== CREATE VIEW [dbo].[vwCategories] AS SELECT C.* FROM [Categories] C ; GO
-- =========================== -- 查出資料庫中所有 Table / View 的關係 -- =========================== SELECT B.* FROM sys.tables A CROSS APPLY ufn_GetDependencyByTable(A.name) B ; GO -- OUTPUT: -- obj_id obj_name obj_type obj_parent -- 565577053 Categories U -- 645577338 vwProducts V Categories -- 1205579333 vwCategories V Categories -- 885578193 vwProductsCookies V vwProducts -- 901578250 vwProductsCake V vwProducts -- 597577167 Products U -- 645577338 vwProducts V Products -- 885578193 vwProductsCookies V vwProducts -- 901578250 vwProductsCake V vwProducts
可以看到
level 0: table:Categories -->
level 1: view: vwProducts -->
level 1: view: vwCategories -->
level 2: view: vwProductsCookies / view: vwProductsCake
level 0: table:Products -->
level 1: view: vwProducts -->
level 2: view: vwProductsCookies / view: vwProductsCake
level 0: table:Categories -->
level 1: view: vwProducts -->
level 1: view: vwCategories -->
level 2: view: vwProductsCookies / view: vwProductsCake
level 0: table:Products -->
level 1: view: vwProducts -->
level 2: view: vwProductsCookies / view: vwProductsCake
但如果要去除重複, 且保有原來關係的先後順序呢? 可以參考以下的 script.
-- =========================== -- 查出資料庫中的 View (去除重複) -- =========================== -- 保留順序, 以便依先後順序, 保留最前面的 View DECLARE @temp1 TABLE ( [obj_id] INT , [obj_name] NVARCHAR(256) , [obj_type] VARCHAR(2) , [obj_parent] NVARCHAR(256) , [seq] int IDENTITY(1,1) ) ; INSERT INTO @temp1 ( [obj_id] , [obj_name] , [obj_type] , [obj_parent] ) SELECT B.* FROM sys.tables A CROSS APPLY ufn_GetDependencyByTable(A.name) B ; -- SELECT * -- FROM @temp1 -- ; -- -- SELECT DISTINCT obj_name -- FROM @temp1 -- ; SELECT seq = MIN(seq), obj_name FROM @temp1 WHERE obj_type = 'V' GROUP BY obj_name ORDER BY MIN(seq), obj_name ; -- OUTPUT: -- seq obj_name -- 2 vwProducts -- 3 vwCategories -- 4 vwProductsCookies -- 5 vwProductsCake --
4.. 結論
綜合上述, SQL Server 資料庫內的物件相依, 錯綜複雜, 要由其中找出相依性, 的確需要一些進階的處理方式.
以上僅供各位參考.
以上僅供各位參考.
5.. 參考文件
(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) SQL Tips, Recursive Queries using Common Table Expressions (CTE) in SQL Server, Edwin Sarmiento, 2008/06/16
(5) SQL Tips, Recursive Queries using Common Table Expressions (CTE) in SQL Server, Edwin Sarmiento, 2008/06/16
(6) 黑暗執行緒, 【茶包射手筆記】在 View 使用 SELECT * 的風險, 2017/02/15
(7) 傑士伯的IT學習之路, [SQL Server] Table新增欄位, 但對應的 View 卻沒有異動 的處理方式 (初階) : 只取1層相依物件, 2018/06/14
沒有留言:
張貼留言