需求场景
我需要统计num列中每一个数字的出现的次数,例如1出现了3次,2出现了两次,5出现了三次。
那么首先我需要将num列通过“,”拆分后展示为多行
sql结果
这里先说答案,随后解释
SELECT
id,
REGEXP_SUBSTR(num, '[^,]+', 1,level) as num
from
numTable
CONNECT BY REGEXP_SUBSTR(num, '[^,]+', 1,level) is not null
and id = prior id
and prior SYS_GUID() is not null
ORDER BY num
在现有结果上对num做分组求和便可以得到每个数字出现的次数了
sql解读
regexp_substr
regexp_substr可以根据标识分割字符串,并取出分割后指定的子字符串
regexp_substr(String, pattern, position,occurrence, modifier)
String: 目标字符串(需要处理的字符串)
pattern: 正则表达式,作为分割的标识
position: 从目标字符串的第几个位置开始解析,默认为1。在oracle中字符串索引也是从1开始的,而不是0
occurrence:取出分割后的字符串数组的第几个子字符串,也是从1开始
modifier:匹配模式,"i"不区分大小写,"c"区分大小写。默认区分大小写
例如:
connect by level
level是树形结构中、表示层级的伪列。从1开始递增,到层级的最深层次结束。与connect by连用
connect by是递归查询的条件
详情查看大佬的文章【精选】Oracle connect by与level的使用_connect by level-优快云博客
对于单独的字符串或单行数据可直接使用如下语法进行拆分
SELECT
REGEXP_SUBSTR('1,2,4,5', '[^,]+', 1,level) as num
from
dual
CONNECT BY REGEXP_SUBSTR('1,2,4,5', '[^,]+', 1,level) is not null
但对于多行数据,使用上述语法往往会得到不尽人意的结果集
例如在如下表中:
执行:
SELECT
id,
REGEXP_SUBSTR(num, '[^,]+', 1,level) as num1
from
numTable
CONNECT BY REGEXP_SUBSTR(num, '[^,]+', 1,level) is not null
得到:
明显多了很多数据,并且受行数影响,结果集数量随行数指数型暴增。为什么会出现这样的结果?
问题分析
是因为level本质是表示的层级数,并不是rownum这种单纯的序列。在connect by条件中若没有指定上下级关系,那么每一行都会作为其它行的父级与子级。
如果没有指定任何递归条件和查询条件,那么结果集应是如下的层级结构
但是条件中限制了"CONNECT BY REGEXP_SUBSTR(num, '[^,]+', 1,level) is not null",也就是"5"是没有第二、第三层级的,所以应该把"5"的第一层级以外的层级及其子层级去掉
再由于“REGEXP_SUBSTR(num, '[^,]+', 1,level) as num1”,把对应层级的字符串分割出来,第一层取第一个数字,第二层取第二个数字...
这样便得到了上面的 5-2-4-1-2-4的顺序。
解决问题
知道了多余的结果是重复递归造成的,所以要加上必要的递归条件过滤多余的结果。
既然原因是没有指定父子连接条件,那就指定一个条件,它限制自己只能是自己的父级或子级
and prior id = id
-- prior的位置在此处不影响结果,它只是决定树的生长方向
-- and id = prior id
但是可以看出这是一个死循环的条件,父子关系相互指向,会导致 5->5->5->5...... 124->124->124... 死循环
加上如下条件,终止死循环(至于为什么能终止循环,原理尚不清楚,待补充)
and PRIOR DBMS_RANDOM.VALUE() IS NOT NULL
-- 需要一个随机性足够大的随机数
-- 在某些场景或框架中,不允许直接调用DBMS_RANDOM.VALUE()
-- 可以使用SYS_GUID()替代
至此得到了期望的结果