Oracle 正则表达式计算由逗号包围的字符串的多次出现

Posted

技术标签:

【中文标题】Oracle 正则表达式计算由逗号包围的字符串的多次出现【英文标题】:Oracle regex count multiple occurrences of a string surrounded by commas 【发布时间】:2020-09-08 13:13:36 【问题描述】:

这个问题类似于我的previous 问题。我正在寻找一种方法来计算 Oracle (11g) SQL 数据库中一列中以逗号分隔的值列表中的字符串。例如,假设我有以下数据:

SELECT ('SL,PK') as col1 FROM dual
    UNION ALL 
SELECT ('SL,CR,SL') as col1 FROM dual
    UNION ALL 
SELECT ('PK,SL') as col1 FROM dual
    UNION ALL 
SELECT ('SL,SL') as col1 FROM dual
    UNION ALL
SELECT ('SL') as col1 FROM dual
    UNION ALL
SELECT ('PK') as col1 FROM dual
    UNION ALL
SELECT ('PI,SL,PK') as col1 FROM dual
    UNION ALL 
SELECT ('PI,SL,SL,PK') as col1 FROM dual
    UNION ALL 
SELECT ('PI,SL,SL,SL,PK') as col1 FROM dual
    UNION ALL 
SELECT ('PI,SL,SL,SL,SL,PK') as col1 FROM dual
    UNION ALL 
SELECT ('PI,OSL,SL,PK') as col1 FROM dual
    UNION ALL 
SELECT ('PI,SL,SLR,PK') as col1 FROM dual

COL1
-----
SL,PK
SL,CR,SL
PK,SL
SL,SL
SL
PK
PI,SL,PK
PI,SL,SL,PK
PI,SL,SL,SL,PK
PI,SL,SL,SL,SL,PK
PI,OSL,SL,PK
PI,SL,SLR,PK

我希望严格计算子字符串“SL”的所有出现次数(即不包括“OSL”、“SLR”等)。 理想的结果应该是这样的

COL1                COL2
-----               -----
SL,PK               1
SL,CR,SL            2
PK,SL               1
SL,SL               2
SL                  1
PK                  0
PI,SL,PK            1
PI,SL,SL,PK         2
PI,SL,SL,SL,PK      3
PI,SL,SL,SL,SL,PK   4
PI,OSL,SL,PK        1
PI,SL,SLR,PK        1

我可以使用lengthregexp_replace 完成此操作:

SELECT 
    col1,
    (length(col1) - NVL(length(regexp_replace(regexp_replace(col1,'(^|,)(SL)($|,)','\1' || '' || '\3',1,0,'imn'),'(^|,)(SL)($|,)','\1' || '' || '\3',1,0,'imn')),0))/length('SL') as col2
FROM (
    SELECT ('SL,PK') as col1 FROM dual
        UNION ALL 
    SELECT ('SL,CR,SL') as col1 FROM dual
        UNION ALL 
    SELECT ('PK,SL') as col1 FROM dual
        UNION ALL 
    SELECT ('SL,SL') as col1 FROM dual
        UNION ALL
    SELECT ('SL') as col1 FROM dual
        UNION ALL
    SELECT ('PK') as col1 FROM dual
        UNION ALL
    SELECT ('PI,SL,PK') as col1 FROM dual
        UNION ALL 
    SELECT ('PI,SL,SL,PK') as col1 FROM dual
        UNION ALL 
    SELECT ('PI,SL,SL,SL,PK') as col1 FROM dual
        UNION ALL 
    SELECT ('PI,SL,SL,SL,SL,PK') as col1 FROM dual
        UNION ALL 
    SELECT ('PI,OSL,SL,PK') as col1 FROM dual
        UNION ALL 
    SELECT ('PI,SL,SLR,PK') as col1 FROM dual
)

COL1                COL2
-----               -----
SL,PK               1
SL,CR,SL            2
PK,SL               1
SL,SL               2
SL                  1
PK                  0
PI,SL,PK            1
PI,SL,SL,PK         2
PI,SL,SL,SL,PK      3
PI,SL,SL,SL,SL,PK   4
PI,OSL,SL,PK        1
PI,SL,SLR,PK        1

但希望有一个更优雅的解决方案,也许是regexp_count。我已经在其他具有单词边界\b 构造可用(使用\bSL\b)的正则表达式实现中成功实现了我的目标,但还没有找到Oracle 正则表达式的解决方案。

【问题讨论】:

你真的应该修复你的数据模型。不要在一个字符串中存储多个值。使用具有单独行的表。这样你就不会花时间去寻找聪明的技巧来做你想做的事了。 我不反对,我更希望数据更接近 3NF,但这不在我的控制范围内,因为我不是我们数据库团队的成员。 col1 是一个字符串,col2 是一个特定子字符串 (SL) 在 col1 中出现的计数。 col1 是多行,每行都有不同的示例字符串。该查询将添加第二列col2,其中包含计数。尝试运行我提供的示例 SQL。我也试图澄清我的目标示例。 【参考方案1】:

如果你破解字符串,你可以使用regexp_count()

select col1, regexp_count(replace(col1, ',', ',,'), '(^|\W)SL(\W|$)')

这将分隔符加倍,因此第一个匹配不会吃掉它——解决了 Oracle 正则表达式不支持前瞻的潜在问题。

Here 是一个 dbfiddle。

【讨论】:

【参考方案2】:

这是一种选择:

SQL> with temp as
  2    (select col1,
  3            regexp_substr(col1, '[^,]+', 1, column_value) val
  4     from test cross join
  5       table(cast(multiset(select level from dual
  6                           connect by level <= regexp_count(col1, ',') + 1
  7                          ) as sys.odcinumberlist))
  8    )
  9  select col1,
 10         sum(case when val = 'SL' then 1 else 0 end) col2
 11  From temp
 12  group by col1;

COL1                    COL2
----------------- ----------
PI,SL,SLR,PK               1
PK,SL                      1
PK                         0
SL,CR,SL                   2
PI,OSL,SL,PK               1
SL,SL                      2
PI,SL,SL,PK                2
PI,SL,SL,SL,PK             3
SL,PK                      1
SL                         1
PI,SL,PK                   1
PI,SL,SL,SL,SL,PK          4

12 rows selected.

SQL>

它有什么作用?

tempCTE 将每一列拆分为行(分隔符为逗号) 最后的select 只计算每个col1SLs 的数量

【讨论】:

我认为这将是我想要的,但我无法将它完全推广到我们的实际表中。当您致电table() 时,究竟发生了什么。如果这是一个过于基本的问题,我深表歉意。 在这里,它用于避免重复,因为我们正在处理许多(好的,多个)行/字符串。如果没有这个,你就不需要它了。 您是否将我的示例数据称为名为@9​​87654327@ 的表? 是的,我将表命名为 TEST。 如果有相同的 col1 值,则此解决方案会将它们组合在一起并返回比表中实际数量更少的行。【参考方案3】:

您可以使用XMLTABLE 溢出字符串然后计数:

SELECT col1,
       (
         SELECT COUNT(*)
         FROM   XMLTABLE(
                  ('"' || REPLACE( col1, ',', '","' ) || '"')
                  COLUMNS
                    value CHAR(2) PATH '.'
                )
         WHERE  value = 'SL'
       ) AS col2
FROM   test_data

所以,对于您的测试数据:

CREATE TABLE test_data ( col1 ) AS
SELECT 'SL,PK'             FROM dual UNION ALL 
SELECT 'SL,CR,SL'          FROM dual UNION ALL 
SELECT 'PK,SL'             FROM dual UNION ALL 
SELECT 'SL,SL'             FROM dual UNION ALL
SELECT 'SL'                FROM dual UNION ALL
SELECT 'PK'                FROM dual UNION ALL
SELECT 'PI,SL,PK'          FROM dual UNION ALL 
SELECT 'PI,SL,SL,PK'       FROM dual UNION ALL 
SELECT 'PI,SL,SL,SL,PK'    FROM dual UNION ALL 
SELECT 'PI,SL,SL,SL,SL,PK' FROM dual UNION ALL 
SELECT 'PI,OSL,SL,PK'      FROM dual UNION ALL 
SELECT 'PI,SL,SLR,PK'      FROM dual

这个输出:

COL1 | COL2 :---------------- | ---: SL,PK | 1 SL,CR,SL | 2 PK,SL | 1 SL,SL | 2 SL | 1 PK | 0 PI,SL,PK | 1 PI,SL,SL,PK | 2 PI,SL,SL,SL,PK | 3 PI,SL,SL,SL,SL,PK | 4 PI,OSL,SL,PK | 1 PI,SL,SLR,PK | 2

db小提琴here

【讨论】:

以上是关于Oracle 正则表达式计算由逗号包围的字符串的多次出现的主要内容,如果未能解决你的问题,请参考以下文章

求一条oracle的正则表达式

如何通过 Oracle 中的正则表达式从逗号分隔列表中删除重复项,但我不想要重复值? [复制]

oracle SQL 正则表达式

java 正则表达式匹配字符串,包含没有数字的单词,并且可以选择用逗号分隔

正则表达式查找所有匹配项,除了那些被字符包围的匹配项

正则表达式用于在不被单引号或双引号包围时使用空格分割字符串