SQL中的行列轉換

    在項目中遇到一個需求,簡化後的描述是這樣的:一個表中存在一個表示種別的列和該種別的值的列
同一時間每種種別最多存在一條數據記錄,同一時間能夠存在多種種別的數據記錄。如今想要查詢指定時間段內的多種種別的數據,每種種別做爲一列,按照時間排序打印到表格中。
    最初採用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 子句>;

相關文章
相關標籤/搜索