SQL Server裏PIVOT運算符的」紅顏禍水「

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

概述

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 records
INSERT 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個數據庫實體到表裏,每一個實體包含多個屬性。在表裏每一個屬性只是額外的記錄。若是你像擴展實體更多的屬性,你只插入額外的記錄到表裏,而沒有必要進行數據庫架構修改——這就是開放數據庫架構的「威力」……express

查詢這樣的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 EAVTable
11 GROUP BY RecordID -- Grouping phase
12 GO

從代碼裏能夠看到,很容易區分每一個階段,還有它們如何映射到T-SQL查詢。下圖給你展現了查詢結果,最後咱們把行轉爲了列。spa

PIVOT運算符

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

 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     Country
10 FROM EAVTable
11 PIVOT(MAX(Value) FOR Element IN (FirstName, LastName, City, Country)) AS t
12 GO

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

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

分組元素是你在PIVOT運算符裏沒有引用的剩下列。在咱們的例子裏,咱們沒有在PIVOT運算符裏沒有引用RecordID列,所以這個列在分組階段(Grouping Phase)被使用。若是咱們隨後修改數據庫架構,這會帶來有趣的反作用,例如對基本表增長額外列:server

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

而後咱們對其賦值:blog

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     Country
10 FROM
11 (
12     -- Table Expression
13     SELECT RecordID, Element, Value FROM EAVTable
14 ) AS t
15 PIVOT(MAX(Value) FOR Element IN (FirstName, LastName, City, Country)) AS t1
16 GO

從代碼裏能夠看到,我經過一個表表達式輸送給PIVOT運算符。並且在表表達式裏,你從表裏只選擇須要的列。這就意味着之後你能夠修改表架構也會破壞PIVOT查詢的結果。

小結

我但願這篇文章已向你展現了在SQL Server裏,爲何PIVOT運算符是很是危險的。這個語法自己帶來了很是高效的代碼,但做爲反作用你不能直接指定分組元素。因次你應該確保使用一個表表達式來定義輸送給PIVOT運算符的列來保證給出結果的肯定性。

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

感謝關注!

參考文章:

https://www.sqlpassion.at/archive/2014/08/25/the-dangerous-beauty-of-the-pivot-operator-in-sql-server/

相關文章
相關標籤/搜索