兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】

系列文章目录

系列文章目录(兼容Oracle与MySQL)


文章目录

  • 系列文章目录
  • 前言
  • 一、LISTAGG
    • 1、单集合聚合功能
    • 2、组集合聚合功能
    • 3、数据分析功能
  • 二、GROUP_CONCAT
  • 总结


前言

针对数据库的查询结果进行分组是很常见的情况,所以就存在了针对分组的结果进行字符串结果拼接的函数,比如Oracle中的LISTAGG和MySQL中的GROUP_CONCAT。本文针对这两个函数的使用以及差异进行分析,以达到在兼容Oracle与MySQL时的结果一致。


提示:以下是本篇文章正文内容,下面案例可供参考

一、LISTAGG

LISTAGG是从Oracle 11.2开始才有的,官方文档参考:https://docs.oracle.com/cd/E11882_01/server.112/e41084/functions089.htm#SQLRF30030

兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第1张图片

按照官方的说明,这个函数的功能就是:根据组将指定的字段按照某种排序进行拼接。比如:
兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第2张图片
在这个查询结果当中,部分编号为20的员工有5个,如果我们想将这些员工的名字都拼接为一个字段,那么就会使用到LISTAGG这个函数了。

1、单集合聚合功能

这是这个函数最常见的功能。

SELECT T.DEPTNO,listagg (T.ENAME, ',') WITHIN GROUP (ORDER BY T.ENAME) names
FROM SCOTT.EMP T WHERE T.DEPTNO = '20' GROUP BY T.DEPTNO;

兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第3张图片
从结果来看,返回结果为1条,而且通过LISTAGG函数聚合结果包含了整个组的员工名称,分割符为,,拼接的结果为ADAMS,FORD,JONES,SCOTT,SMITH

  • measure_expr 结果表达式

这个语句当中包含了三个比较重要的元素,首先就是measure_expr,也就是上面listagg函数体中的T.ENAME,这个参数可以是任意的语句,但是空值会被忽略。比如以下的案例将measure_expr修改为T.SAL

SELECT T.DEPTNO,listagg (T.SAL, ',') WITHIN GROUP (ORDER BY T.ENAME) names
FROM SCOTT.EMP T WHERE T .DEPTNO = '20' GROUP BY T .DEPTNO;

兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第4张图片
此时拼接的结果变为了1100,3000,2975,3000,800

  • delimiter_expr 分割符表达式

分隔符表达式用于指定分组数据拼接使用的分割符,在上面的案例当中我们使用的是,,在下面我们修改为:;

SELECT T .DEPTNO,listagg (T .ENAME, ':') WITHIN GROUP (ORDER BY T.ENAME) names
FROM SCOTT.EMP T WHERE T .DEPTNO = '20' GROUP BY T .DEPTNO;

兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第5张图片

此时拼接的结果变为了ADAMS:FORD:JONES:SCOTT:SMITH

  • order_by_clause 排序结果表达式

ADAMS,FORD,JONES,SCOTT,SMITH这个拼接结果首先是通过员工的名称排序之后再拼接的结果,在上面的LISTAGG中是由ORDER BY T.ENAME这个order_by_clause 语句来指定的,当然也可以指定其他字段,比如按照工资来进行排序。

SELECT T .DEPTNO,listagg (T .ENAME, ',') WITHIN GROUP (ORDER BY T.SAL) names
FROM SCOTT.EMP T WHERE T .DEPTNO = '20' GROUP BY T .DEPTNO;

兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第6张图片
此时聚合的结果就变为了SMITH,ADAMS,JONES,FORD,SCOTT。如果指定的字段是一个常量,结果是什么样的呢?
兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第7张图片
兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第8张图片
如果从第一个结果看好像是排序好的,但是从第二个结果来看,其实结果未进行排序,其实返回的结果应该是按照数据库中的行号来的,也就是硬盘中本来存储的顺序。

另外从上面的结果,我们也不难得出另一个结论:这个函数没法针对返回的结果进行重复值过滤。1100,2975,3000,3000,800这个结果当中包含了3000这个重复值。

2、组集合聚合功能

上面的结果返回的都是一条数据,因为我们通过WHERE T.DEPTNO = '20' GROUP BY T .DEPTNO限定后的组只有一条,如果去掉这个where条件呢?此时结果如下

SELECT T.DEPTNO,listagg (T .ENAME, ',') WITHIN GROUP (ORDER BY T.ENAME) names
FROM SCOTT.EMP T GROUP BY T.DEPTNO ORDER BY T.DEPTNO;

兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第9张图片
其实这个功能只是上面单集合聚合功能在不同组上的叠加结果而已,没有啥特别的。只是要注意返回的结果为多条。从这里也可以得出一个结论:LISTAGG函数并不能保证返回结果的数据条数,关键还在于过滤之后的组的数量,如果通过where过滤之后进行分组只有一组,那么返回结果就是一条,否则就是多条。

3、数据分析功能

比如查询某个部分的员工信息如下所示
兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第10张图片
我们希望某一列可以包含有字符串类型的统计信息,比如当前部门的员工名称的拼接符。通过关键字PARTITION来实现。

SELECT
	 T.EMPNO,T.ENAME,T.JOB,T.HIREDATE,T.SAL,T.DEPTNO
	 ,listagg (T.ENAME, ',') WITHIN GROUP (ORDER BY T.ENAME)  over(PARTITION BY T.DEPTNO) deptEmps
FROM
	SCOTT.EMP T WHERE T .DEPTNO = '20' ;

兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第11张图片
同样可以包含多个部门(去掉where或者修改where子句)

SELECT
	 T.EMPNO,T.ENAME,T.JOB,T.HIREDATE,T.SAL,T.DEPTNO
	 ,listagg (T.ENAME, ',') WITHIN GROUP (ORDER BY T.ENAME)  over(PARTITION BY T.DEPTNO) deptEmps
FROM
	SCOTT.EMP T 

查询所有员工以及针对员工信息做统计
兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第12张图片
查询所有员工以及针对员工工资做统计
兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第13张图片

二、GROUP_CONCAT

在MySQL当中,并不存在LISTAGG函数,可以用于替代的是另一个函数GROUP_CONCAT,GROUP_CONCAT在MySQL当中是作为一个聚合函数的。对应的官方文档参考:
https://dev.mysql.com/doc/refman/5.6/en/aggregate-functions.html。
对应的语法如下(这个函数从5.5就开始有了)

GROUP_CONCAT([DISTINCT] expr [,expr ...]
[ORDER BY {unsigned_integer | col_name | expr}
[ASC | DESC] [,col_name ...]]
[SEPARATOR str_val])

在这里同样涉及到measure_exprdelimiter_exprorder_by_clause,分别对应上面的exprSEPARATORORDER BY.但是与Oracle不同的是,MySQL中除了expr之外其他都是可选的,Oracle当中,只有delimiter_expr是可选的。比如在MySQL当中
兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第14张图片
只指定measure_expr

SELECT t.DEPTNO,GROUP_CONCAT(t.ENAME) FROM EMP t WHERE t.DEPTNO = '20' GROUP BY t.DEPTNO;

在这里插入图片描述
此处没有指定分割符,默认为,,没有进行排序,所以按照数据在硬盘中正常存储拼接,不进行任何排序。在Oracle当中,如果不指定分隔符,其实默认的为空字符串。如下所示
兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第15张图片
接下来在MySQL当中指定分隔符和排序规则.

SELECT t.DEPTNO,GROUP_CONCAT(t.ENAME ORDER BY t.ENAME SEPARATOR ':') FROM EMP t WHERE t.DEPTNO = '20' GROUP BY t.DEPTNO;

兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第16张图片
返回的聚合结果为:ADAMS:FORD:JONES:SCOTT:SMITH,可以看出,这里和Oracle中的单集合聚合功能是一模一样的。

修改为按照工资进行聚合

SELECT t.DEPTNO,GROUP_CONCAT(t.SAL ORDER BY t.ENAME SEPARATOR ':') FROM EMP t WHERE t.DEPTNO = '20' GROUP BY t.DEPTNO;

兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第17张图片
由于这里SAL字段是数字类型的,Oracle与MySQL中针对数字类型的处理方式不同(前者不保留小数中的0,而MySQL保留小数中的0)。所以拼接的字符串多了一堆的0.与Oracle是不同的。另外MySQL当中还可以通过加入DISTINCT关键字来去重。
在这里插入图片描述
这个去重的效果在Oracle当中是不提供的。

由于组集合聚合功能与单集合聚合功能的本质是一样的,所以MySQL也是可以的.
兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第18张图片
由于MySQL中这个函数中没有PARTITION类似的关键字,数据分析功能需要通过子表关联来做

SELECT E.EMPNO,E.ENAME,E.JOB,E.HIREDATE,E.SAL,E.DEPTNO,TE.GC AS deptEmps FROM EMP E INNER JOIN
( SELECT t.DEPTNO,GROUP_CONCAT(DISTINCT t.ENAME ORDER BY t.ENAME SEPARATOR ':') AS GC FROM EMP t GROUP BY t.DEPTNO) TE
ON E.DEPTNO = TE.DEPTNO ORDER BY DEPTNO;

兼容Oracle与MySQL的那些事【LISTAGG与GROUP_CONCAT】_第19张图片


总结

通过以上的分析我们不难得出如下的结论:LISTAGG与GROUP_CONCAT要实现的目标大致都是相同的,针对查询的结果分组、排序然后按照分隔符拼接成结果字符串,但是要做到Oracle与MySQL的绝对兼容必须把握几点:
1. MySQL的GROUP_CONCAT中不要使用DISTINCT关键字来去重,因为Oracle的LISTAGG不支持去重功能
2. 仅仅针对字符串(VARCHAR)类型字段拼接,因为MySQL中拼接数字会产生意想不到的结果(小数位中无用的0也会拼接)
3. 必须明确指定分割符,GROUP_CONCAT默认分割符为,,而LISTAGG默认分隔符为空字符串。

你可能感兴趣的:(oracle,sql,mysql,数据库)