国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 數據庫 > SQL Server > 正文

SQL Server里PIVOT運算符的”紅顏禍水“

2024-08-31 00:54:11
字體:
來源:轉載
供稿:網友
SQL Server里PIVOT運算符的”紅顏禍水“

在今天的文章里我想討論下SQL Server里一個特別的T-SQL語言結構——自SQL Server 2005引入的PIVOT運算符。我經常引用這個與語言結構是SQL Server里最危險的一個——很快你就會知道為什么。在我們進入特定問題和陷阱前,首先我想給你下使用SQL Server里的PIVOT能實現什么的一個基本概述。

概述

SQL Server里PIVOT運算符背后的基本思想是在T-SQL查詢期間,你可以旋轉行為列。運算符本身是SQL Server 2005后引入的,主要用在基于建立在實體屬性值模型(Entity Attribute Value model (EAV))原則上的數據庫。EAM模型背后的想法是你可以擴展數據庫實體,而不需要進行數據庫架構的修改。因此EAV模型存儲實體的所有屬性以鍵/值對存儲在一個表里。我們來看下面一個簡單的鍵/值對模型的表。

CREATE TABLE EAVTable(    RecordID INT NOT NULL,    Element CHAR(100) NOT NULL,    Value SQL_VARIANT NOT NULL,    PRIMARY KEY (RecordID, Element))GO-- Insert some recordsINSERT INTO EAVTable (RecordID, Element, Value) VALUES(1, 'FirstName', 'Woody'),(1, 'LastName', 'Tu'),(1, 'City', 'Linhai'),(1, 'Country', 'China'),(2, 'FirstName', 'Bill'),(2, 'LastName', 'Gates'),(2, 'City', 'Seattle'),(2, 'Country', 'USA')GO

如你所見,我們插入2個數據庫實體到表里,每個實體包含多個屬性。在表里每個屬性只是額外的記錄。如果你像擴展實體更多的屬性,你只插入額外的記錄到表里,而沒有必要進行數據庫架構修改——這就是開放數據庫架構的“威力”……

查詢這樣的EAV表顯然很困難,因為你處理的是平鍵/值對的數據結構。因此你要旋轉表內容,行旋轉為列。你可以進行用自帶的PIVOT運算符進行這個旋轉,或者通過傳統的CASE表達式進行純手工來實現。在我們進入PIVOT細節前,我想給你展示下通過手工使用T-SQL和一些CASE表達式來實現。如果你手工進行旋轉,你的T-SQL查詢需要實現3個階段:

  1. 分組階段(Grouping Phase)
  2. 攤開階段(Spreading Phase)
  3. 聚合階段(Aggregation Phase)

分組階段(Grouping Phase)我們壓縮我們的EAV表為不同的數據庫實體。在這里我們在RecordID列進行一個GROUP BY。在第2階段的,攤開階段(Spreading Phase),我們使用多個CASE表達式來旋轉行為列。最后在聚合階段(Aggregation Phase)我們使用MAX表達式來為每個行和列返回不同值。我們來看下列T-SQL代碼。

 1 -- Pivot the data with a handwritten T-SQL statement. 2 -- Make sure you have an index defined on the grouping column. 3 SELECT 4     RecordID, 5     -- Spreading and aggregation phase 6     MAX(CASE WHEN Element = 'FirstName' THEN Value END) AS 'FirstName', 7     MAX(CASE WHEN Element = 'LastName' THEN Value END) AS 'LastName', 8     MAX(CASE WHEN Element = 'City' THEN Value END) AS 'City', 9     MAX(CASE WHEN Element = 'Country' THEN Value END) AS 'Country'10 FROM EAVTable11 GROUP BY RecordID -- Grouping phase12 GO

從代碼里可以看到,很容易區分每個階段,還有它們如何映射到T-SQL查詢。下圖給你展示了查詢結果,最后我們把行轉為了列。

PIVOT運算符

自SQL Server 2005起(差不多10年前了!),微軟在T-SQL里引入PIVOT運算符。使用那個運算符你可以進行同樣的轉換(行到列),只要一個原生運算符即可。聽起來很簡單,很有前景,不是么?下列代碼顯示了使用原生PIVOT運算符進行同樣的轉換。

 1 -- Perform the same query with the native PIVOT Operator. 2 -- The grouping column is not specified explicitly, it's the remaining column 3 -- that is not referenced in the spreading and aggregation elements. 4 SELECT 5     RecordID, 6     FirstName, 7     LastName, 8     City, 9     Country10 FROM EAVTable11 PIVOT(MAX(Value) FOR Element IN (FirstName, LastName, City, Country)) AS t12 GO

當你執行那個查詢時,你會收到和剛才圖片一樣的結果。但當你看PIVOT運算符語法時,和手動方法相比,你會看到一個很大的區別:

你只能指定分攤和聚合元素!不能明確定義分組元素!

分組元素是你在PIVOT運算符里沒有引用的剩下列。在我們的例子里,我們沒有在PIVOT運算符里沒有引用RecordID列,因此這個列在分組階段(Grouping Phase)被使用。如果我們隨后修改數據庫架構,這會帶來有趣的副作用,例如對基本表增加額外列:

1 -- Add a new column to the table2 ALTER TABLE EAVTable ADD SomeData CHAR(1)3 GO

然后我們對其賦值:

1 UPDATE dbo.EAVTable SET SomeData=LEFT(CAST(Value AS VARCHAR(1)),1)

現在當你執行用PIVOIT運算符的同個查詢時(在那somedata列都有非NULL值),你會拿回完全不同的結果,因為排序階段現在是在RecordIDSomeData列(我們剛加的)上。

相比如果我們重新執行我們剛開始寫的手工T-SQL查詢會發生什么。它還是返回同樣正確的結果。這是在SQL Server里,PIVOT運算符的其中一個最大的副作用:分組元素不能明確定義。為了克服這個問題,最佳實踐是使用只返回需要列的表表達式。使用這個方法,如果你隨后修改表架構還是沒有問題,因從表表達式默認情況下額外的列還是沒有返回。我們來看下列的代碼:

 1 -- Use a table expression to state explicitly which columns you want to  2 -- return from the base table. Therefore you can always control on which 3 -- columns the PIVOT operator is performing the grouping. 4 SELECT 5     RecordID, 6     FirstName, 7     LastName, 8     City, 9     Country10 FROM11 (12     -- Table Expression13     SELECT RecordID, Element, Value FROM EAVTable14 ) AS t15 PIVOT(MAX(Value) FOR Element IN (FirstName, LastName, City, Country)) AS t116 GO

從代碼里可以看到,我通過一個表表達式輸送給PIVOT運算符。而且在表表達式里,你從表里只選擇需要的列。這就意味著以后你可以修改表架構也會破壞PIVOT查詢的結果。

小結

我希望這篇文章已向你展示了在SQL Server里,為什么PIVOT運算符是非常危險的。這個語法本身帶來了非常高效的代碼,但作為副作用你不能直接指定分組元素。因次你應該確保使用一個表表達式來定義輸送給PIVOT運算符的列來保證給出結果的確定性。

用PIVOT運算符你有什么經歷?你是否喜歡它?如果你不喜歡它,你想要什么改變?

感謝關注!


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 石泉县| 上虞市| 蓝田县| 施甸县| 拜城县| 临桂县| 墨江| 甘孜县| 宝兴县| 博乐市| 封丘县| 类乌齐县| 伊川县| 长春市| 威远县| 黎川县| 公主岭市| 阳东县| 疏勒县| 赤城县| 思茅市| 黑水县| 安阳县| 共和县| 肇源县| 大石桥市| 南丹县| 博罗县| 长乐市| 玛纳斯县| 临城县| 泸水县| 福贡县| 乌苏市| 潜江市| 建昌县| 天祝| 绥中县| 来宾市| 揭西县| 青阳县|