1. 程式人生 > >SQL行列轉換6種方法

SQL行列轉換6種方法

ati cin 列名 ada ott 計劃 ase mit all

在進行報表開發時,很多時候會遇到行列轉換操作,很對開發人員針對於SQL級別行列轉換操作一直不甚理解,今天正好抽空對其進行了一些簡單的總結。這裏主要列舉3種可以實現SQL行列轉換的方法,包括通用SQL解法以及Oracle支持解法。

一、測試數據

測試表依舊采用Oracle經典的scott模式下的dept表和emp表,結構如下:

DEPT:

create table DEPT

(

DEPTNO NUMBER(2) not null,

DNAME VARCHAR2(14),

LOC VARCHAR2(13)

)

-- Create/Recreate primary, unique and foreign key constraints

alter table DEPT

add constraint PK_DEPT primary key (DEPTNO)

using index;

SQL> select * from dept;

DEPTNO DNAME LOC

------ -------------- -------------

10 ACCOUNTING NEW YORK

20 RESEARCH DALLAS

30 SALES CHICAGO

40 OPERATIONS BOSTON

EMP:

create table EMP

(

EMPNO NUMBER(4) not null,

ENAME VARCHAR2(10),

JOB VARCHAR2(9),

MGR NUMBER(4),

HIREDATE DATE,

SAL NUMBER(7,2),

COMM NUMBER(7,2),

DEPTNO NUMBER(2),

SEX VARCHAR2(2) default ‘男‘

)

-- Create/Recreate primary, unique and foreign key constraints

alter table EMP

add constraint PK_EMP primary key (EMPNO)

using index;

alter table EMP

add constraint FK_DEPTNO foreign key (DEPTNO)

references DEPT (DEPTNO);

SQL> select * from emp;

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO SEX

----- ---------- --------- ----- ----------- --------- --------- ------ ---

7369 SMITH CLERK 7902 1980/12/17 800.00 20 男

7499 ALLEN SALESMAN 7698 1981/2/20 1600.00 300.00 30 女

7521 WARD SALESMAN 7698 1981/2/22 1250.00 500.00 30 女

7566 JONES MANAGER 7839 1981/4/2 2975.00 20 女

7654 MARTIN SALESMAN 7698 1981/9/28 1250.00 1400.00 30 女

7698 BLAKE MANAGER 7839 1981/5/1 2850.00 30 女

7782 CLARK MANAGER 7839 1981/6/9 2450.00 10 女

7788 SCOTT ANALYST 7566 1987/4/19 3000.00 20 男

7839 KING PRESIDENT 1981/11/17 5000.00 10 女

7844 TURNER SALESMAN 7698 1981/9/8 1500.00 0.00 30 女

7876 ADAMS CLERK 7788 1987/5/23 1100.00 20 男

7900 JAMES CLERK 7698 1981/12/3 950.00 30 女

7902 FORD ANALYST 7566 1981/12/3 3000.00 20 女

7934 MILLER CLERK 7782 1982/1/23 1300.00 10 男

14 rows selected

二、行列轉換方法

需求:查詢每個部門中不同職位總工資。

1、最傳統的SQL解法如下:

SQL> select d.dname, e.job, sum(sal) as sum_sal

2 from emp e, dept d

3 where e.deptno = d.deptno

4 group by d.dname, e.job;

DNAME JOB SUM_SAL

-------------- --------- ----------

SALES MANAGER 2850

SALES CLERK 950

ACCOUNTING MANAGER 2450

ACCOUNTING PRESIDENT 5000

ACCOUNTING CLERK 1300

RESEARCH MANAGER 2975

SALES SALESMAN 5600

RESEARCH ANALYST 6000

RESEARCH CLERK 1900

9 rows selected

以上方法確實得到了不同部門不同職位的工資總和,可是我們看到每一個部門出現了多行信息,這並不是我們想要的結果,我們更希望每個部門顯示為1行。

2、使用子查詢實現行列轉換:

SQL> select e.deptno,

2 (select sum(sal) from emp where emp.deptno = e.deptno and job=‘ANALYST‘) as analyst_job,

3 (select sum(sal) from emp where emp.deptno=e.deptno and job=‘CLERK‘) as clerk_job,

4 (select sum(sal) from emp where emp.deptno=e.deptno and job=‘MANAGER‘) as manager_job,

5 (select sum(sal) from emp where emp.deptno=e.deptno and job=‘PRESIDENT‘) as persident_job,

6 (select sum(sal) from emp where emp.deptno=e.deptno and job=‘SALESMAN‘) as salesman_job

7 from emp e

8 group by e.deptno;

DEPTNO ANALYST_JOB CLERK_JOB MANAGER_JOB PERSIDENT_JOB SALESMAN_JOB

------ ----------- ---------- ----------- ------------- ------------

30 950 2850 5600

20 6000 1900 2975

10 1300 2450 5000

3、嘗試更改SQL如以下形式達到目的:

SQL> select DNAME,

2 SUM(MANAGER_JOB) AS SUM_MANAGER_SAL,

3 SUM(PRESIDENT_JOB) AS SUM_PRESIDENT_SAL,

4 SUM(CLERK_JOB) AS SUM_CLERK_SAL,

5 SUM(ANALYST_JOB) AS SUM_ANALYST_SAL,

6 SUM(SALESMAN_JOB) AS SUM_SALESMAN_SAL

7 from (select d.dname,

8 (select sum(sal)

9 from emp

10 where emp.empno = e.empno

11 and emp.job = ‘MANAGER‘) as MANAGER_JOB,

12 (select sum(sal)

13 from emp

14 where emp.empno = e.empno

15 and emp.job = ‘PRESIDENT‘) as PRESIDENT_JOB,

16 (select sum(sal)

17 from emp

18 where emp.empno = e.empno

19 and emp.job = ‘CLERK‘) as CLERK_JOB,

20 (select sum(sal)

21 from emp

22 where emp.empno = e.empno

23 and emp.job = ‘ANALYST‘) as ANALYST_JOB,

24 (select sum(sal)

25 from emp

26 where emp.empno = e.empno

27 and emp.job = ‘SALESMAN‘) as SALESMAN_JOB

28 from emp e, dept d

29 where e.deptno = d.deptno) temp

30 group by temp.dname

31 order by temp.dname;

DNAME SUM_MANAGER_SAL SUM_PRESIDENT_SAL SUM_CLERK_SAL SUM_ANALYST_SAL SUM_SALESMAN_SAL

-------------- --------------- ----------------- ------------- --------------- ----------------

ACCOUNTING 2450 5000 1300

RESEARCH 2975 1900 6000

SALES 2850 950 5600

通過查詢結果可以看到,這裏我們已經實現了行列轉換,同時這個結果也符合我們日常閱讀習慣。但是代碼過於復雜,執行計劃回表次數明顯很會很多。這裏的代碼基本符合任何一種支持SQL的數據庫基本語法,屬於通用模式,那麽有沒有簡單一點的方法呢?答案是肯定的!

4、基於Oracle decode()函數的行列轉換:

SQL> select d.dname,

2 sum(decode(job, ‘MANAGER‘, sal, 0)) as SUM_MANAGER_SAL,

3 sum(decode(job, ‘PRESIDENT‘, sal, 0)) as SUM_PRESIDENT_SAL,

4 sum(decode(job, ‘CLERK‘, sal, 0)) as SUM_CLERK_SAL,

5 sum(decode(job, ‘ANALYST‘, sal, 0)) as SUM_ANALYST_SAL,

6 sum(decode(job, ‘SALESMAN‘, sal, 0)) as SUM_SALESMAN_SAL

7 from emp e, dept d

8 where e.deptno = d.deptno

9 group by d.dname;

DNAME SUM_MANAGER_SAL SUM_PRESIDENT_SAL SUM_CLERK_SAL SUM_ANALYST_SAL SUM_SALESMAN_SAL

-------------- --------------- ----------------- ------------- --------------- ----------------

ACCOUNTING 2450 5000 1300 0 0

RESEARCH 2975 0 1900 6000 0

SALES 2850 0 950 0 5600

根據上面執行結果,我們可以看到通過Oracle數據庫的decode()函數達到了進行行列裝換的目的,而且明顯針對於前一種行列轉換操作效率提升不少。

5、通用行列轉換可以通過case when ... then ...else ... end實現:

SQL> select d.dname,

2 sum(case

3 when e.job = ‘ANALYST‘ then

4 sal

5 else

6 0

7 end) as ANALYST_JOB,

8 sum(case

9 when e.job = ‘CLERK‘ then

10 sal

11 else

12 0

13 end) as CLERK_JOB,

14 sum(case

15 when e.job = ‘MANAGER‘ then

16 sal

17 else

18 0

19 end) as MANAGER_JOB,

20 sum(case

21 when e.job = ‘PRESIDENT‘ then

22 sal

23 else

24 0

25 end) as PRESIDENT_JOB,

26 sum(case

27 when e.job = ‘SALESMAN‘ then

28 sal

29 else

30 0

31 end) as SALESMAN_JOB

32 from emp e, dept d

33 where e.deptno = d.deptno

34 group by d.dname;

DNAME ANALYST_JOB CLERK_JOB MANAGER_JOB PRESIDENT_JOB SALESMAN_JOB

-------------- ----------- ---------- ----------- ------------- ------------

ACCOUNTING 0 1300 2450 5000 0

RESEARCH 6000 1900 2975 0 0

SALES 0 950 2850 0 5600

6、在Oracle 11g中引入了pivot和unpivot函數功能,專門用來進行行列轉換,pivot函數語法如下:

select *|列 [別名],...

from 子查詢

pivot(

統計函數(列)s for 轉換列名稱 in(

內容1 [[as] 別名],

內容2 [[as] 別名],

... ...

內容n [[as] 別名]

)

)

[where 條件(s)]

[group by 分組字段1,分組字段2,... ...]

[having 過濾條件(s)]

[order by 排序字段 asc|desc]

使用pivot函數實現行列轉換:

SQL> select *

2 from (select d.dname, e.job, e.sal

3 from emp e, dept d

4 where e.deptno = d.deptno) pivot(sum(sal) for job in(‘ANALYST‘ AS

5 ANALYST_JOB,

6 ‘CLERK‘ AS

7 CLERK_JOB,

8 ‘MANAGER‘ AS

9 MANAGER_JOB,

10 ‘PRESIDENT‘ AS

11 PRESIDENT_JOB,

12 ‘SALESMAN‘ AS

13 SALESMAN_JOB))

14 order by dname;

DNAME ANALYST_JOB CLERK_JOB MANAGER_JOB PRESIDENT_JOB SALESMAN_JOB

-------------- ----------- ---------- ----------- ------------- ------------

ACCOUNTING 1300 2450 5000

RESEARCH 6000 1900 2975

SALES 950 2850 5600

以上SQL使用pivot函數實現了行列轉換操作。至此,對於行列轉換操作我們使用3種方法實現。

以下是pivot函數實現更復雜一些SQL的例子:

6.1、查詢每個部門的總工資,每個部門不同職位的總工資和每個部門的最高工資和最低工資:

SQL> select *

2 from (select deptno,

3 job,

4 sal,

5 sum(sal) over(partition by deptno) as sum_sal,

6 max(sal) over(partition by deptno) as max_sal,

7 min(sal) over(partition by deptno) as min_sal

8 from emp) pivot(sum(sal) for job in(‘ANALYST‘ AS ANALYST_JOB,

9 ‘CLERK‘ AS CLERK_JOB,

10 ‘MANAGER‘ AS MANAGER_JOB,

11 ‘PRESIDENT‘ AS PRESIDENT_JOB,

12 ‘SALESMAN‘ AS SALESMAN_JOB))

13 order by deptno;

DEPTNO SUM_SAL MAX_SAL MIN_SAL ANALYST_JOB CLERK_JOB MANAGER_JOB PRESIDENT_JOB SALESMAN_JOB

------ ---------- ---------- ---------- ----------- ---------- ----------- ------------- ------------

10 8750 5000 1300 1300 2450 5000

20 10875 3000 800 6000 1900 2975

30 9400 2850 950 950 2850 5600

6.2、查詢每個部門不同職位總工資以及每個部門不同職位最高工資:

SQL> select *

2 from (select deptno, job, sal from emp)

3 pivot(sum(sal) as sum_sal, max(sal) as sum_max

4 for job in(‘ANALYST‘ AS ANA_JOB,

5 ‘CLERK‘ AS CLE_JOB,

6 ‘MANAGER‘ AS MAN_JOB/*,

7 ‘PRESIDENT‘ AS PRE_JOB,

8 ‘SALESMAN‘ AS SAL_JOB*/)

9 )

10 order by deptno;

DEPTNO ANA_JOB_SUM_SAL ANA_JOB_SUM_MAX CLE_JOB_SUM_SAL CLE_JOB_SUM_MAX MAN_JOB_SUM_SAL MAN_JOB_SUM_MAX

------ --------------- --------------- --------------- --------------- --------------- ---------------

10 1300 1300 2450 2450

20 6000 3000 1900 1100 2975 2975

30 950 950 2850 2850

6.3、統計每個部門不同職位總工資和不同性別的最高工資:

SQL> select *

2 from (select deptno, job, sex, sal from emp)

3 pivot(sum(sal) as sum_sal, max(sal) as sum_max

4 for(job, sex) in((‘MANAGER‘, ‘男‘) AS MANAGER_JOB_MAN,

5 (‘MANAGER‘, ‘女‘) AS MANAGER_JOB_WOMAN/*,

6 (‘CLERK‘, ‘男‘) AS CLERK_JOB_MAN,

7 (‘CLERK‘, ‘女‘) AS CLERK_JOB_WOMAN,

8 (‘ANALYST‘, ‘男‘) AS ANALYST_JOB_MAN,

9 (‘ANALYST‘, ‘女‘) AS ANALYST_JOB_WOMAN,

10 (‘PRESIDENT‘, ‘男‘) AS PRESIDENT_JOB_MAN,

11 (‘PRESIDENT‘, ‘女‘) AS PRESIDENT_JOB_WOMAN,

12 (‘SALESMAN‘, ‘男‘) AS SALESMAN_JOB_MAN,

13 (‘SALESMAN‘, ‘女‘) AS SALESMAN_JOB_WOMAN*/))

14 order by deptno;

DEPTNO MANAGER_JOB_MAN_SUM_SAL MANAGER_JOB_MAN_SUM_MAX MANAGER_JOB_WOMAN_SUM_SAL MANAGER_JOB_WOMAN_SUM_MAX

------ ----------------------- ----------------------- ------------------------- -------------------------

10 2450 2450

20 2975 2975

30 2850 2850

6.4、如果在pivot中增加xml顯示,可以利用any設置所要操作的所有數據,目前any僅能使用在xml顯示中:

SQL> select *

2 from (select deptno, job, sal from emp) pivot xml(sum(sal) for job in(any))

3 order by deptno;

6.5、pivot功能是將行轉換為列;同樣unpivot功能進行列轉行操作,操作語法如下:

select *|列 [別名],...

from 子查詢

unpivot [include nulls|exclude nulls(默認)](

統計函數(列)s for 轉換列名稱 in(

內容1 [[as] 別名],

內容2 [[as] 別名],

... ...

內容n [[as] 別名]

)

)

[where 條件(s)]

[group by 分組字段1,分組字段2,... ...]

[having 過濾條件(s)]

[order by 排序字段 asc|desc]

如把步驟2中行轉列的SQL結果再轉回行:

SQL> with temp as

2 (select *

3 from (select d.dname, e.job, e.sal

4 from emp e, dept d

5 where e.deptno = d.deptno) pivot(sum(sal) for job in(‘ANALYST‘ AS

6 ANALYST_JOB,

7 ‘CLERK‘ AS

8 CLERK_JOB,

9 ‘MANAGER‘ AS

10 MANAGER_JOB,

11 ‘PRESIDENT‘ AS

12 PRESIDENT_JOB,

13 ‘SALESMAN‘ AS

14 SALESMAN_JOB))

15 order by dname)

16 select *

17 from temp unpivot(sum_sal for job in(ANALYST_JOB as ‘ANALYST‘,

18 CLERK_JOB as ‘CLERK‘,

19 MANAGER_JOB as ‘MANAGER‘,

20 PRESIDENT_JOB as ‘PRESIDENT‘,

21 SALESMAN_JOB as ‘SALESMAN‘));

DNAME JOB SUM_SAL

-------------- --------- ----------

ACCOUNTING CLERK 1300

ACCOUNTING MANAGER 2450

ACCOUNTING PRESIDENT 5000

RESEARCH ANALYST 6000

RESEARCH CLERK 1900

RESEARCH MANAGER 2975

SALES CLERK 950

SALES MANAGER 2850

SALES SALESMAN 5600

9 rows selected

從上述結果可以看到,這裏的結果已經將列轉換為行了,但是不包含nulls值得行,如果想包含nulls值的行業顯示出來需要顯示的生命include nulls關鍵字:

SQL> with temp as

2 (select *

3 from (select d.dname, e.job, e.sal

4 from emp e, dept d

5 where e.deptno = d.deptno) pivot(sum(sal) for job in(‘ANALYST‘ AS

6 ANALYST_JOB,

7 ‘CLERK‘ AS

8 CLERK_JOB,

9 ‘MANAGER‘ AS

10 MANAGER_JOB,

11 ‘PRESIDENT‘ AS

12 PRESIDENT_JOB,

13 ‘SALESMAN‘ AS

14 SALESMAN_JOB))

15 order by dname)

16 select *

17 from temp unpivot include nulls(sum_sal for job in(ANALYST_JOB as

18 ‘ANALYST‘,

19 CLERK_JOB as ‘CLERK‘,

20 MANAGER_JOB as

21 ‘MANAGER‘,

22 PRESIDENT_JOB as

23 ‘PRESIDENT‘,

24 SALESMAN_JOB as

25 ‘SALESMAN‘));

DNAME JOB SUM_SAL

-------------- --------- ----------

ACCOUNTING ANALYST

ACCOUNTING CLERK 1300

ACCOUNTING MANAGER 2450

ACCOUNTING PRESIDENT 5000

ACCOUNTING SALESMAN

RESEARCH ANALYST 6000

RESEARCH CLERK 1900

RESEARCH MANAGER 2975

RESEARCH PRESIDENT

RESEARCH SALESMAN

SALES ANALYST

SALES CLERK 950

SALES MANAGER 2850

SALES PRESIDENT

SALES SALESMAN 5600

15 rows selected

SQL行列轉換6種方法