Oracle 的 SQL 中没有直接的动态透视方法,除非它返回 XML 类型的结果。对于非 XML 结果,可以通过创建SYS_REFCURSOR
返回类型的函数来使用 PL/SQL
使用条件聚合
CREATE OR REPLACE FUNCTION Get_Jobs_ByYear RETURN SYS_REFCURSOR IS
v_recordset SYS_REFCURSOR;
v_sql VARCHAR2(32767);
v_cols VARCHAR2(32767);
BEGIN
SELECT LISTAGG( 'SUM( CASE WHEN job_title = '''||job_title||''' THEN 1 ELSE 0 END ) AS "'||job_title||'"' , ',' )
WITHIN GROUP ( ORDER BY job_title )
INTO v_cols
FROM ( SELECT DISTINCT job_title
FROM jobs j );
v_sql :=
'SELECT "HIRE YEAR",'|| v_cols ||
' FROM
(
SELECT TO_NUMBER(TO_CHAR(hire_date,''YYYY'')) AS "HIRE YEAR", job_title
FROM employees e
JOIN jobs j
ON j.job_id = e.job_id
)
GROUP BY "HIRE YEAR"
ORDER BY "HIRE YEAR"';
OPEN v_recordset FOR v_sql;
DBMS_OUTPUT.PUT_LINE(v_sql);
RETURN v_recordset;
END;
/
带有PIVOT条款
CREATE OR REPLACE FUNCTION Get_Jobs_ByYear RETURN SYS_REFCURSOR IS
v_recordset SYS_REFCURSOR;
v_sql VARCHAR2(32767);
v_cols VARCHAR2(32767);
BEGIN
SELECT LISTAGG( ''''||job_title||''' AS "'||job_title||'"' , ',' )
WITHIN GROUP ( ORDER BY job_title )
INTO v_cols
FROM ( SELECT DISTINCT job_title
FROM jobs j );
v_sql :=
'SELECT *
FROM
(
SELECT TO_NUMBER(TO_CHAR(hire_date,''YYYY'')) AS "HIRE YEAR", job_title
FROM employees e
JOIN jobs j
ON j.job_id = e.job_id
)
PIVOT
(
COUNT(*) FOR job_title IN ( '|| v_cols ||' )
)
ORDER BY "HIRE YEAR"';
OPEN v_recordset FOR v_sql;
DBMS_OUTPUT.PUT_LINE(v_sql);
RETURN v_recordset;
END;
/
但是使用 ORA-01489 编码有一个缺点:当第一个参数中的连接字符串超过4000 个字符的长度时,字符串连接的结果太长就会引发。在这种情况下,返回变量值的查询可能会替换为嵌套在其中的函数,例如LISTAGG()
v_cols
XMLELEMENT()
XMLAGG()
CREATE OR REPLACE FUNCTION Get_Jobs_ByYear RETURN SYS_REFCURSOR IS
v_recordset SYS_REFCURSOR;
v_sql VARCHAR2(32767);
v_cols VARCHAR2(32767);
BEGIN
SELECT RTRIM(DBMS_XMLGEN.CONVERT(
XMLAGG(
XMLELEMENT(e, 'SUM( CASE WHEN job_title = '''||job_title||
''' THEN 1 ELSE 0 END ) AS "'||job_title||'",')
).EXTRACT('//text()').GETCLOBVAL() ,1),',') AS "v_cols"
FROM ( SELECT DISTINCT job_title
FROM jobs j);
v_sql :=
'SELECT "HIRE YEAR",'|| v_cols ||
' FROM
(
SELECT TO_NUMBER(TO_CHAR(hire_date,''YYYY'')) AS "HIRE YEAR", job_title
FROM employees e
JOIN jobs j
ON j.job_id = e.job_id
)
GROUP BY "HIRE YEAR"
ORDER BY "HIRE YEAR"';
DBMS_OUTPUT.put_line(LENGTH(v_sql));
OPEN v_recordset FOR v_sql;
RETURN v_recordset;
END;
/
除非超过VARCHAR2类型的上限32767。最后一种方法也可能适用于Oracle 11g 第 2版之前版本的数据库,因为它们不包含函数。 LISTAGG()
顺便说一句,如果数据库的版本是 12.2+,LISTAGG()
则可以在检查v_cols
生成的非常长的连接字符串时使用函数,而不会出现ORA-01489错误,而如果数据库的版本是12.2+ ,则通过使用ON OVERFLOW TRUNCATE子句截断字符串的尾随部分如
LISTAGG( <concatenated string>,',' ON OVERFLOW TRUNCATE 'THE REST IS TRUNCATED' WITHOUT COUNT )
该函数可以调用为
VAR rc REFCURSOR
EXEC :rc := Get_Jobs_ByYear;
PRINT rc
从SQL Developer的命令行
或者
BEGIN
:result := Get_Jobs_ByYear;
END;
从PL/SQL Developer的测试窗口中获取结果集。
Demo for generated queries