在项目中遇到一个需求,简化后的描述是这样的:一个表中存在一个表示种别的列和该种别的值的列
同一时间每种种别最多存在一条数据记录,同一时间能够存在多种种别的数据记录。如今想要查询指定时间段内的多种种别的数据,每种种别做为一列,按照时间排序打印到表格中。
最初采用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 子句>;