行列轉換(Pivot/Unpivot)是報表開發常見需求。Oracle 11g+ 內建 PIVOT/UNPIVOT 語法,但舊版本或複雜動態欄位仍需用 DECODE/CASE、分析函數或 PL/SQL 實作。本文分三類場景完整示範。
STUDENT SUBJECT GRADE -------- -------- ------ student1 語文 80 student1 數學 70 student1 英語 60 student2 語文 90 student2 數學 80 student2 英語 100
STUDENT 語文 數學 英語 -------- ---- ---- ---- student1 80 70 60 student2 90 80 100
select student, sum(decode(subject, '語文', grade)) "語文", sum(decode(subject, '數學', grade)) "數學", sum(decode(subject, '英語', grade)) "英語" from score_table group by student;
11g+ 可用 PIVOT:
select * from (
select student, subject, grade from score_table
)
pivot (max(grade) for subject in ('語文' as "語文", '數學' as "數學", '英語' as "英語"));
C1 C2 --- --------- 1 我 1 是 1 誰 2 知 2 道 3 不
1 我是誰 2 知道 3 不
create or replace function get_c2(p_c1 number) return varchar2 is
v_result varchar2(4000);
begin
for rec in (select c2 from t where c1 = p_c1 order by c2) loop
v_result := v_result || rec.c2;
end loop;
return v_result;
end;
/
select distinct c1, get_c2(c1) c2 from t;
select c1,
substr(max(sys_connect_by_path(c2, ';')), 2) name
from (
select c1, c2,
row_number() over (order by c2) rn,
lead(row_number() over (order by c2)) over (partition by c1 order by c2) rn1
from t
)
start with rn1 is null
connect by rn1 = prior rn
group by c1;
select c1, listagg(c2, '') within group (order by c2) c2 from t group by c1;
CLASS1 CALLDATE CALLCOUNT ------ ---------- ---------- 1 2005-08-08 40 1 2005-08-07 6 2 2005-08-08 77 3 2005-08-09 33 3 2005-08-08 9 3 2005-08-07 21
CALLDATE CALLCNT1 CALLCNT2 CALLCNT3 ---------- -------- -------- -------- 2005-08-09 0 0 33 2005-08-08 40 77 9 2005-08-07 6 0 21
-- 1. 建立 Ref Cursor 型別套件
create or replace package pkg_getrecord is
type myrctype is ref cursor;
end;
/
-- 2. 動態 PIVOT 函數
create or replace function fn_rs return pkg_getrecord.myrctype is
v_sql varchar2(4000);
v_cur pkg_getrecord.myrctype;
cursor c1 is
select ',sum(case when class1=' || class1 || ' then callcount else 0 end) "CallCount' || class1 || '"' col
from t group by class1;
begin
v_sql := 'select calldate ';
for rec in c1 loop
v_sql := v_sql || rec.col;
end loop;
v_sql := v_sql || ' from t group by calldate order by calldate desc';
open v_cur for v_sql;
return v_cur;
end;
/
-- 3. SQL*Plus 測試
var results refcursor;
exec :results := fn_rs;
print results;
DECODE(value, if1, then1, if2, then2, ..., else) -- 等同於 CASE WHEN value=if1 THEN then1 WHEN value=if2 THEN then2 ... ELSE else END -- 取兩值中較小者 select decode(sign(a-b), -1, a, b) from dual; -- sign() 回傳:正數=1, 0=0, 負數=-1
| Oracle 版本 | 推薦做法 |
|---|---|
| 11gR2+ | PIVOT / UNPIVOT / LISTAGG |
| 10g~11gR1 | DECODE/CASE + GROUP BY / SYS_CONNECT_BY_PATH |
| 9i 以前 | PL/SQL 函數 / MAX(DECODE...) / 自訂聚合 |