SQL 从Oracle中生成CSV格式的输出,你想把一个表里的数据转换成某种分隔列表形式(例如,以逗号作为分隔符)。例如,对于 EMP
表,你希望得到如下所示的结果集。
SQL 从Oracle中生成CSV格式的输出 问题描述
你想把一个表里的数据转换成某种分隔列表形式(例如,以逗号作为分隔符)。例如,对于 EMP
表,你希望得到如下所示的结果集。
假设你正在使用 Oracle 数据库(Oracle Database 10g 或后续的版本),并希望借助 MODEL
子句实现本问题的解决方案。
SQL 从Oracle中生成CSV格式的输出 解决方案
本解决方案利用了 Oracle 的 MODEL
子句提供的迭代功能。这里用到的技巧是,使用窗口函数 ROW_NUMBER OVER
对每个 DEPTNO
对应的员工进行编号(按照 EMPNO
排序,不过按照哪个字段排序并不重要)。因为 MODEL
支持以数组形式访问行值,我们可以通过把序号减 1
实现对前一个数组元素的访问。因此,对于每一行,我们要创建一个列表,该列表包含了当前员工的名字,并加上编号小于当前员工的那些人的名字。
SQL 从Oracle中生成CSV格式的输出 扩展知识
首先使用窗口函数 LAG OVER
读取前一个员工(按照 EMPNO
排序)的 DEPTNO
。结果按照 DEPTNO
分区,因此对于每个部门的第一个员工(按照 EMPNO
排序)返回值将是 Null
,对于其余员工而言,返回值则是本部门的 DEPTNO
。结果集如下所示。
下一步仔细观察 MODEL
子句的 MEASURES
部分。MEASURES
列表里的项目是如下的几个数组。
ENAME
:一个包含EMP
表里全部ENAME
值的数组。PRIOR_DEPTNO
:由窗口函数LAG OVER
返回值构成的数组。CNT
:由每个DEPTNO
对应的员工人数构成的数组。RNK
:每个DEPTNO
分组内每个员工的编号(按照EMPNO
排序)构成的数组。
数组索引是 DEPTNO
和 RN
(在 DIMENSION BY
子句里调用窗口函数 ROW_NUMBER OVER
得到的返回值)。如果希望看到上述这些数组里实际包含了哪些值,只要注释掉 MODEL
子句的 RULES
部分代码并执行查询即可,如下所示。
现在我们弄清楚 MODEL
子句里声明的那些项目了,接下来看一下 RULES
部分的代码。CASE
表达式负责评估 PRIOR_DEPTNO
的当前值。如果当前值是 Null
,表明它是每个 DEPTNO
分组内的第一个员工,那么该员工的 ENAME
会被作为当前员工的 LIST
。如果 PRIOR_DEPTNO
值不是 Null
,那么就把前一个员工的 LIST
值附加上当前员工的名字(ENAME
数组),然后把结果作为当前员工的 LIST
。针对 DEPTNO
分组内的每一行记录执行该 CASE
表达式操作的话,就会得到一个迭代的、逗号分隔的值列表(即 CSV
格式的输出)。如下所示的例子里打印出了中间结果。
最后要过滤掉其他员工,只保留每个 DEPTNO
分组内的最后一个员工,这样才能确保每个 DEPTNO
都能得到一个完整的 CSV
列表。保存在 CNT
和 RN
数组里的值能实现这一点。RN
代表每个 DEPTNO
分组内按照 EMPNO
排序后得到的员工编号,因此每个 DEPTNO
分组内最后一个员工就是满足 CNT = RN
条件的那个员工,如下所示。