由一个SQL问题引发的:WM_CONCAT、LISTAGG的用法
详细数据如图一,含义为名字为甲乙丙的三人不限次数任意参加python、c++、java考试,得到的分数等级在第三列。
现需要筛选出只参过一种考试的人名和科目,结果应如图二。在oracle下SQL该如何写?

图一

图二
1 | CREATE TABLE temp_table (name varchar(255), course varchar(255), score varchar(255)); |
思路一:首先在name、course两列做去重处理。然后以name做分组,过滤出统计数量为一的记录。最后在进行关联得到name、course两列。
A1:最后关联的步骤使用join连接的方式。1
2
3
4
5
6
7
8
9
10
11
12
13SELECT
DISTINCT t.name,
tt.course
FROM
(SELECT
name
FROM (
SELECT DISTINCT name, course
FROM TEMP_TABLE
)
GROUP BY name
HAVING count(1) = 1) t
INNER JOIN TEMP_TABLE tt ON t.name = tt.name
A2:感觉A1的方式有点啰嗦,所以最后的步骤使用的嵌套子查询。1
2
3
4
5
6
7
8
9
10SELECT
t.name,
(SELECT DISTINCT course FROM TEMP_TABLE WHERE name = t.name) course
FROM (
SELECT
DISTINCT name,
course
FROM TEMP_TABLE) t
GROUP BY t.name
HAVING count(1) = 1
觉得思路一总的来看还是有点啰嗦,故发给小伙伴们讨论更优解。思路二就是来自小伙伴的想法。
思路二:首先使用函数把name相同的course列以特殊字符拼接在一行中,然后在此基础上过滤出没有特殊字符的记录。
A3:使用WM_CONCAT函数,可以在函数中直接做去重处理。1
2
3
4
5
6
7
8
9SELECT
name,
course
FROM (
SELECT name, WM_CONCAT(DISTINCT course) course
FROM TEMP_TABLE t
GROUP BY t.name
)
WHERE course NOT LIKE '%,%'
A4:使用LISTAGG函数,要提前做去重处理再使用函数。1
2
3
4
5
6
7
8
9
10
11
12SELECT
name,
course
FROM (
SELECT t.name, LISTAGG(t.course, ';') WITHIN GROUP (ORDER BY t.name, t.course) course
FROM (
SELECT DISTINCT name, course
FROM TEMP_TABLE
) t
GROUP BY t.name
)
WHERE course NOT LIKE '%;%'
总结:WM_CONCAT、LISTAGG的用法
- WM_CONCAT不出现在12c中,所以不建议使用这个函数。但是还是列出了它的不同用法。
1 | SELECT |

1 | SELECT |

- LISTAGG在11.2版本之后可以使用。

对于指定的列,LISTAGG对子ORDER BY句中指定的每个组内的数据进行排序,然后连接列的值。
- 作为单集聚合函数,
LISTAGG对所有行进行操作并返回单个输出行。 - 作为一个组集聚合,该函数操作并返回该
GROUPBY子句定义的每个组的输出行。 - 作为分析函数,
LISTAGG根据中的一个或多个表达式将查询结果集划分为组query_partition_clause。
函数的参数遵循以下规则:
measure_expr可以是任何列。度量列中的空值将被忽略。delimiter_expr指定是分开测量的值的字符串。该子句是可选的,默认为NULL。- 在
order_by_clause决定了被返回连接值的顺序。仅当ORDERBY列列表实现唯一排序时,该功能才是确定性的。
如果列是RAW类型,返回数据类型是RAW,否则返回值是VARCHAR2类型。