在項目中遇到一個需求,簡化後的描述是這樣的:一個表中存在一個表示種別的列和該種別的值的列
同一時間每種種別最多存在一條數據記錄,同一時間能夠存在多種種別的數據記錄。如今想要查詢指定時間段內的多種種別的數據,每種種別做爲一列,按照時間排序打印到表格中。
最初採用join的方式,將每種種別的數據檢索出來鏈接在一塊兒。數據種別少的時候還能忍受,當種別增多
的時候,SQL文就至關長了,而且仍是大段類似的SQL文,既不易維護也容易出錯。所以考慮捨棄這種寫法,調查尋找更好的寫法。
一種比較簡潔的方法是採用case或者decode,能夠減小不少SQL文,另外一種更簡便的方式則是使用行列變換函數pivot和unpivot。爲了說明不一樣的寫法,下面會建立兩個簡化的表結構進行說明。
(注:pivot和unpivot在oracle11以後的版本支持。)
表 1:sql
create table test1 ( addday varchar2(10), addtime varchar2(8), itemname varchar2(10), itemvalue number(4) )
其中itemname的值是各類各樣的種別,itemvalue是該種別對應的值。
表格中的值以下(值1):
addday addtime itemname itemvalue
1 2016/07/18 09:00 ITEM4 60
2 2016/08/08 01:00 ITEM1 0
3 2016/08/08 01:00 ITEM2 1
4 2016/08/08 01:00 ITEM3 2
5 2016/08/08 01:00 ITEM4 3
6 2016/08/08 02:00 ITEM1 2
7 2016/08/08 02:00 ITEM2 2
8 2016/08/08 02:00 ITEM3 2
9 2016/08/08 02:00 ITEM4 2
10 2016/08/08 03:00 ITEM1 5
11 2016/08/08 03:00 ITEM2 4
12 2016/08/08 03:00 ITEM3 2
13 2016/08/08 03:00 ITEM4 3
14 2016/08/08 04:00 ITEM1 12
15 2016/08/08 04:00 ITEM2 20
16 2016/08/08 04:00 ITEM3 44
17 2016/08/08 04:00 ITEM4 22oracle
想要達到的查詢效果是:
addday addtime ITEM1 ITEM2 ITEM3 ITEM4函數
方法一:code
SELECT ADDDAY, ADDTIME, MAX(CASE ITEMNAME WHEN 'ITEM1' THEN ITEMVALUE ELSE NULL END) ITEM1, MAX(CASE ITEMNAME WHEN 'ITEM2' THEN ITEMVALUE ELSE NULL END) ITEM2, MAX(CASE ITEMNAME WHEN 'ITEM3' THEN ITEMVALUE ELSE NULL END) ITEM3, MAX(CASE ITEMNAME WHEN 'ITEM4' THEN ITEMVALUE ELSE NULL END) ITEM4 FROM TEST1 GROUP BY ADDDAY, ADDTIME ORDER BY ADDDAY, ADDTIME
檢索結果以下(值2):
addday addtime ITEM1 ITEM2 ITEM3 ITEM4
1 2016/07/18 09:00 60
2 2016/08/08 01:00 0 1 2 3
3 2016/08/08 02:00 2 2 2 2
4 2016/08/08 03:00 5 4 2 3
5 2016/08/08 04:00 12 20 44 22排序
方法二:it
SELECT * FROM TEST1 PIVOT( MAX(ITEMVALUE) FOR ITEMNAME IN('ITEM1' AS ITEM1, 'ITEM2' AS ITEM2, 'ITEM3' AS ITEM3, 'ITEM4' AS ITEM4) ) ORDER BY ADDDAY, ADDTIME
檢索結果同上(值2)。
注:這裏因爲符合條件的只有一個值,所以MAX(ITEMVALUE) 中使用MAX,SUM等沒有區別,
僅由於語法要求有聚合函數,更復雜或者其餘需求的場合,須要採用合適的聚合函數。io
上面是行變列的轉換,下面再進行列變行的轉換。
表2:table
create table test2 ( addday varchar2(10), addtime varchar2(8), item1 number(4), item2 number(4), item3 number(4), item4 number(4) ) insert into test2 (addday, addtime, item1, item2, item3, item4) values ('2016/07/18', '09:00', null, null, null, 60); insert into test2 (addday, addtime, item1, item2, item3, item4) values ('2016/08/08', '01:00', 0, 1, 2, 3); insert into test2 (addday, addtime, item1, item2, item3, item4) values ('2016/08/08', '02:00', 2, 2, 2, 2); insert into test2 (addday, addtime, item1, item2, item3, item4) values ('2016/08/08', '03:00', 5, 4, 2, 3); insert into test2 (addday, addtime, item1, item2, item3, item4) values ('2016/08/08', '04:00', 12, 20, 44, 22);
數據採用上述檢索轉換後的結果,看是否能夠再轉換成最初的樣子。
一般的方法是採用union all的方法,下面僅舉例說明函數unpivot的使用:class
SELECT * FROM TEST2 UNPIVOT( ITEMVALUE FOR ITEMNAME IN(ITEM1,ITEM2,ITEM3,ITEM4) )
檢索結果同上(值1)。test
備註:
PIVOT用於將列值旋轉爲列名(即行轉列),語法以下:
table_source
PIVOT(
聚合函數(value_column)
FOR pivot_column
IN(<column_list>)
)
帶有批註的描述:
SELECT <非轉換的列>,
[第一個轉換的列] AS <列名稱>,
[第二個轉換的列] AS <列名稱>,
...
[最後一個轉換的列] AS <列名稱>,
FROM
(<生成數據的 SELECT 查詢>)
AS <源查詢的別名>
PIVOT
(
<聚合函數>(<要聚合的列>)
FOR
[<包含要成爲列標題的值的列>]
IN ( [第一個轉換的列], [第二個轉換的列],
... [最後一個轉換的列])
) AS <轉換表的別名>
<可選的 ORDER BY 子句>;
UNPIVOT用於將列明轉爲列值(即列轉行),UNPIVOT並不徹底是PIVOT的逆操做。 PIVOT 會執行一次聚合,從而將多個可能的行合併爲輸出中的單個行。而UNPIVOT 不會重現原始表值表達式的結果,由於行已經被合併了。 語法以下: table_source UNPIVOT( value_column FOR pivot_column IN(<column_list>) ) 帶有批註的描述: SELECT <非轉換的列>, [要生成的值的列] AS <列名稱>, [包含列標題要轉換爲的列] AS <列名稱> FROM (<生成數據的 SELECT 查詢>) AS <源查詢的別名> UNPIVOT ( (<要生成的值的列>) FOR [<包含列標題要轉換爲的列>] IN ( [第一個轉換的列標題], [第二個轉換的列標題], ... [最後一個轉換的列標題]) ) AS <轉換表的別名> <可選的 ORDER BY 子句>;