前言
上一節我們簡單講述了表表達式的4種類型,這一系列我們來講講使用視圖的限制,簡短的內容,深入的理解,Always to review the basics。
避免在視圖中使用ORDER BY
上一節我們也講述了使用表表達式必須滿足的3個要求,其中就有一個無法保證順序,也就是說的ORDER BY的問題,我們還是重點看看在視圖中的限制。在常規查詢中對於排序我們是這樣做的。
USE AdventureWorks2012 GO SELECT * FROM Sales.SalesOrderDetail ORDER BY SalesOrderDetailID DESC
接下來我們在視圖中對數據進行排序,我們創建視圖來看看
USE AdventureWorks2012 GO IF EXISTS (SELECT * FROM sys.views WHERE OBJECT_ID = OBJECT_ID(N'[dbo].[view_limit]')) DROP VIEW [dbo].[view_limit] GO CREATE VIEW view_limit AS SELECT * FROM Sales.SalesOrderDetail ORDER BY SalesOrderDetailID DESC GO
此時當我們執行創建視圖時會發現如下錯誤
此時在視圖內部不能使用ORDER BY我們創建視圖后在外部視圖使用ORDER BY看看
USE AdventureWorks2012 GO IF EXISTS (SELECT * FROM sys.views WHERE OBJECT_ID = OBJECT_ID(N'[dbo].[view_limit]')) DROP VIEW [dbo].[view_limit] GO CREATE VIEW view_limit AS SELECT * FROM Sales.SalesOrderDetail GO SELECT * FROM view_limit ORDER BY SalesOrderDetailID DESC
我們再來看看上述在視圖內部進行ORDER BY時出現的錯誤,它說明可以使用TOP、OFFSET等,接下來我們利用TOP來看看實際結果是怎樣的。
USE AdventureWorks2012 GO IF EXISTS (SELECT * FROM sys.views WHERE OBJECT_ID = OBJECT_ID(N'[dbo].[view_limit]')) DROP VIEW [dbo].[view_limit] GO CREATE VIEW view_limit AS SELECT TOP 100 PERCENT * FROM Sales.SalesOrderDetail ORDER BY SalesOrderDetailID DESC GO
我們再來查詢該視圖看看返回的結果集
USE AdventureWorks2012 GO SELECT * FROM dbo.view_limit
當我們在創建視圖時內部使用ORDER BY對結果集進行降序,結果返回的數據壓根沒有進行降序,同時我們看到查詢計划根本沒有出現Sort排序操作。我們再來看另外一種情況將返回的數據設置為比100%少一點試試看。
IF EXISTS (SELECT * FROM sys.views WHERE OBJECT_ID = OBJECT_ID(N'[dbo].[view_limit]')) DROP VIEW [dbo].[view_limit] GO CREATE VIEW view_limit AS SELECT TOP 99.9 PERCENT * FROM Sales.SalesOrderDetail ORDER BY SalesOrderDetailID DESC GO
此時則進行了降序排序,說明在視圖中利用TOP、OFFSET就好使了呢?上述查詢我們沒有做任何條件限制,我們查查表中總共有多少數據和利用視圖查詢時返回有多少數據看看。
USE AdventureWorks2012 GO SELECT COUNT(*) AS originalCount FROM Sales.SalesOrderDetail SELECT COUNT(*) AS viewCount FROM dbo.view_limit
雖然在上述情況下我們限制返回的數據最終也按照降序來進行排序,這是相對於小表而言,如果表中數據量比較大的話,此時通過在視圖中進行ORDER BY的話將會缺省很多值,所以建議不要在視圖中進行ORDER BY而是在視圖外部進行ORDER BY。好了這是我們說的第一種限制,我們給出結論。
(1)避免在視圖內部使用ORDER BY,當表數據比較小時雖然通過TOP或OFFSET等能解決問題,但是當數據量比較大時此時在視圖內部使用ORDER BY會導致更多的數據行缺失,建議在視圖外部進行ORDER BY。
避免在視圖中使用SELECT *
首先我們通過創建視圖來看問題的出現。
USE AdventureWorks2012 GO IF EXISTS (SELECT * FROM sys.views WHERE OBJECT_ID = OBJECT_ID(N'[dbo].[view_limit]')) DROP VIEW [dbo].[view_limit] GO CREATE VIEW view_limit AS SELECT * FROM HumanResources.Shift GO
接下來我們通過查找原表和視圖的方式來看看返回的數據
USE AdventureWorks2012 GO -- 查找原表 SELECT * FROM HumanResources.[Shift] GO -- 查找視圖 SELECT * FROM view_limit GO
恩,沒毛病,接下來我們在表中添加額外列
USE AdventureWorks2012
GO
ALTER TABLE HumanResources.[Shift]
ADD AdditionalCol INT
GO
我們再來進行上述查詢,看看返回的結果集
此時我們發現添加額外列后視圖並未顯示,當然數據也就不會顯示了。此時我們在用視圖查詢之前進行刷新看看
USE AdventureWorks2012 GO -- 查找原表 SELECT * FROM HumanResources.[Shift] GO EXEC sp_refreshview 'view_limit'
-- 查找視圖 SELECT * FROM view_limit GO
此時才能返回正確的結果。那么是什么原因導致添加額外列通過視圖查詢會出現意想不到的結果呢,因為視圖在編譯方式上對列是枚舉的,並且新的表列不會自動添加到視圖中,也就是說若我們額外添加了列,此時列根本不會添加到視圖中,所以此時我們可以通過sp_refreshview或sp_refreshsqlmodule的方式來刷新視圖的元數據。所以我們結論如下
(2)避免在視圖中使用SELECT *,當表中添加額外列后會導致視圖中不會自動進行添加,雖然我們可以通過sp_refreshview或sp_refreshmodule的方式來刷新視圖,但是為了避免混淆,最好是在視圖定義中顯式列出所需要的列的名稱,若添加了額外列,同時在視圖中我們需要額外列的話,我們通過ALTER VIEW的方式來修改視圖定義即可。
視圖查詢返回額外列通過JOIN表導致查詢性能低效
下面我們直接通過例子進行演示。
IF EXISTS (SELECT * FROM sys.views WHERE OBJECT_ID = OBJECT_ID(N'[dbo].[view_limit]')) DROP VIEW [dbo].[view_limit] GO CREATE VIEW view_limit AS SELECT [SalesOrderID],[SalesOrderDetailID],[CarrierTrackingNumber] ,[OrderQty],sod.[ProductID],[SpecialOfferID],[UnitPrice],[UnitPriceDiscount] ,[LineTotal],[ReferenceOrderID] FROM Sales.SalesOrderDetail sod INNER JOIN Production.TransactionHistory th ON sod.SalesOrderID = th.ReferenceOrderID GO
解下來我們進行常規SQL查詢和視圖查詢
USE AdventureWorks2012 GO SELECT * FROM dbo.view_limit WHERE SalesOrderDetailID > 111111 GO SELECT [SalesOrderID],[SalesOrderDetailID],[CarrierTrackingNumber] ,[OrderQty],sod.[ProductID],[SpecialOfferID],[UnitPrice],[UnitPriceDiscount] ,[LineTotal],[ReferenceOrderID] FROM Sales.SalesOrderDetail sod INNER JOIN Production.TransactionHistory th ON sod.SalesOrderID = th.ReferenceOrderID WHERE SalesOrderDetailID > 111111 GO
上述利用常規查詢和視圖查詢開銷樣,但是現在我們有這樣一個場景上述視圖是被其他同事所寫,但是當我們用時還需要返回額外其他列,所以為了不返回其他多余的數據而和同事撕逼,我們需要再次在視圖外部進行JOIN來得到我們額外的列,我們下面來看看。
USE AdventureWorks2012 GO SELECT v1.* ,th.[Quantity] FROM dbo.view_limit v1 INNER JOIN Production.TransactionHistory th ON v1.SalesOrderID = th.ReferenceOrderID WHERE SalesOrderDetailID > 111111 GO SELECT [SalesOrderID],[SalesOrderDetailID],[CarrierTrackingNumber] ,[OrderQty],sod.[ProductID],[SpecialOfferID],[UnitPrice],[UnitPriceDiscount] ,[LineTotal],[ReferenceOrderID] ,th.[Quantity] FROM Sales.SalesOrderDetail sod INNER JOIN Production.TransactionHistory th ON sod.SalesOrderID = th.ReferenceOrderID WHERE SalesOrderDetailID > 111111 GO
此時額外返回了Quantity列對視圖再次進行JOIN,我們看看查詢計划開銷
此時發現利用視圖查詢開銷更多,而常規查詢不過是多添加一個列而已沒有任何改變。我們繼續往下看
默認情況下在視圖上創建索引無效
我們在前面一直討論過關於索引的建立的問題,而且索引都是建立在表上,那么我們將索引建立在視圖上情況是怎樣的呢,是不是查詢效率會得到提升呢?我們首先創建測試表並插入數據
USE AdventureWorks2012 GO IF EXISTS (SELECT * FROM sys.objects WHERE OBJECT_ID = OBJECT_ID(N'[dbo].[ViewTable]') AND TYPE IN (N'U')) DROP TABLE [dbo].[ViewTable] GO CREATE TABLE ViewTable (ID1 INT, ID2 INT, SomeData VARCHAR(100))
INSERT INTO ViewTable (ID1,ID2,SomeData) SELECT TOP 100000 ROW_NUMBER() OVER (ORDER BY o1.name), ROW_NUMBER() OVER (ORDER BY o2.name), o2.name FROM sys.all_objects o1 CROSS JOIN sys.all_objects o2 GO
上述我們創建了測試的視圖表ViewTable並插入了10萬條測試數據,接下來我們對表建立索引。
USE AdventureWorks2012
GO
CREATE UNIQUE CLUSTERED INDEX [idx_original_table] ON dbo.ViewTable
(
ID1 ASC
)
接下來我們來創建視圖並在視圖上創建索引
USE AdventureWorks2012
GO
CREATE VIEW ViewLimit
WITH SCHEMABINDING
AS
SELECT ID1,ID2,SomeData
FROM dbo.ViewTable
GO
CREATE UNIQUE CLUSTERED INDEX [idx_view_table] ON [dbo].[ViewLimit]
(
ID2 ASC
)
GO
上述我們需要注意,當在視圖上創建索引時必須指定WITH SCHAMABINDING,否則不允許在視圖上創建索引。我們最后通過常規查詢和視圖查詢來看看查詢計划情況
USE AdventureWorks2012
GO
SELECT ID1,ID2,SomeData
FROM dbo.ViewTable
GO
SELECT ID1,ID2,SomeData
FROM dbo.ViewLimit
GO
此時我們發現視圖查詢利用的索引不是我們創建的索引idx_view_table,主要原因是因為視圖和表是關聯的,所以查詢計划決定在表上的索引比在視圖上創建的索引更加高效。 當我們在WITH中強制指定noexpand此時將會執行在視圖上創建的索引,因為此時視圖已經和原始表沒有關系,它是獨立的,如下:
USE AdventureWorks2012
GO
SELECT ID1,ID2,SomeData
FROM dbo.ViewTable
GO
SELECT ID1,ID2,SomeData
FROM dbo.ViewLimit
WITH(NOEXPAND)
GO
在視圖上創建索引這個問題比較復雜,我們就不討論了,一般通過常規查詢都能解決的問題何必勞駕視圖呢。這個我們需要注意一下就行。
總結
本節我們講了幾個使用視圖時的限制以及建議等問題,下節我們還是會討論使用視圖的其他限制,簡短的內容,深入的理解,我們下節再會。