Oracle SQL:检查指定的单词是否存在于逗号分隔的字符串中

Oracle SQL : Check if specified words are present in comma separated string

我有一个 SQL 函数,它 return 给我一串逗号分隔的国家/地区代码。

我已经在另一个 table 中配置了一些特定的代码,以后我可能会删除或添加更多代码。

我想检查逗号分隔的字符串是否只是那些特定国家/地区代码的组合。也就是说,如果该字符串甚至有一个国家代码而不是指定的国家代码,它应该 return true.

假设我在静态数据中配置了两行table GB 和CH。然后我需要以下结果:

String from function result
GB false
CH false
GB,CH false
CH,GB false
GB,FR true
FR,ES true
ES,CH true
CH,GB,ES true

我是Oracle 19c,只能使用这个版本的功能。另外,我希望它得到优化。就像我可以检查字符串中值的数量,然后计算每个特定代码。如果不匹配,则显然存在一些其他代码。但是我不想使用循环。

谁能给我一个更好的选择。

您可以将 csv 列转换为 table 并使用 EXISTS。例如

with tbl(id,str) as
( 
SELECT 1,'GB,CH' FROM DUAL UNION ALL
SELECT 2,'GB,CH,FR' FROM DUAL UNION ALL
SELECT 3,'GB' FROM DUAL 
),
countries (code) as
(SELECT 'GB' FROM DUAL UNION ALL
 SELECT 'CH' FROM DUAL 
)

select t.* ,
     case when exists (
        select 1 
        from xmltable(('"' || REPLACE(str, ',', '","') || '"')) s  
        where trim(s.column_value) not in (select code from countries)
      ) 
      then 'true' else 'false' end flag
from tbl t

一个方案是逐个匹配国家代码,然后根据提供的字面量作为参数判断是否存在多出的non-matched个国家

下面的 FULL JOIN 将有助于考虑上述逻辑

WITH
  FUNCTION with_function(i_countries VARCHAR2) RETURN VARCHAR2 IS
    o_val VARCHAR2(10);
  BEGIN
     SELECT CASE WHEN SUM(NVL2(t.country_code,0,1))=0 THEN 'false' 
                 ELSE 'true'
                  END
       INTO o_val           
       FROM (SELECT DISTINCT REGEXP_SUBSTR(i_countries,'[^ ,]+',1,level) AS country
               FROM dual
            CONNECT BY level <= REGEXP_COUNT(i_countries,',')+1) tt
       FULL JOIN t
              ON tt.country = t.country_code;      
    RETURN o_val;
  END;
SELECT with_function(<comma-seperated-parameter-list>) AS result
  FROM dual

Demo

假设静态 table 中的所有国家代码以及 comma-separated 字符串中的所有标记始终恰好是 two-letter 字符串,您可以这样做:

with
  static_data(country_code) as (
    select 'GB' from dual union all
    select 'CH' from dual
  )
, sample_inputs(string_from_function) as (
    select 'GB'       from dual union all
    select 'CH'       from dual union all
    select 'GB,CH'    from dual union all
    select 'CH,GB'    from dual union all
    select 'GB,FR'    from dual union all
    select 'FR,ES'    from dual union all
    select 'ES,CH'    from dual union all
    select 'CH,GB,ES' from dual
  )
select string_from_function,
       case when regexp_replace(string_from_function,
                   ',| |' || (select listagg(country_code, '|')
                                       within group (order by null)
                              from   static_data))
                 is null then 'false' else 'true' end as result
from   sample_inputs
;

输出:

STRING_FROM_FUNCTION   RESULT  
---------------------- --------
GB                     false   
CH                     false   
GB,CH                  false   
CH,GB                  false   
GB,FR                  true    
FR,ES                  true    
ES,CH                  true    
CH,GB,ES               true

正则表达式将静态数据 table 中的逗号 space 和每个 two-letter 国家代码替换为 null。如果整个事情的结果是null,那么csv中的所有编码都在静态table中;这就是你需要测试的。

假设保证像 GBCH 这样的代币(对于像“Great Barrier Country Heat”这样的国家)不会被错误地认为是 OK,因为 GB 和 CH 分别是 OK。

这是一种解决方案

with cte as
(select distinct
s,regexp_substr(s, '[^,]+',1, level) code from strings
    
 connect by regexp_substr(s, '[^,]+', 1, level) is not null
)
select 
s string,min(case when exists
  (select * from countries
   where cod = code) then 'yes'
   else 'no'end) all_found
from cte
group by s
order by s;
STRING | ALL_FOUND
:----- | :--------
CH     | yes      
CH,GB  | yes      
ES     | no       
ES,CH  | no       
FR     | no       
GB     | yes      
GB,CH  | yes      
GB,ES  | no       

db<>fiddle here

如果静态 table 中的值数量较少,那么最简单的方法可能不是从函数中拆分值,而是从静态 table 中生成所有值的组合使用:

SELECT SUBSTR(SYS_CONNECT_BY_PATH(value, ','), 2) AS combination
FROM   static_table
CONNECT BY NOCYCLE PRIOR value != value;

其中,对于示例数据:

CREATE TABLE static_table(value) AS
SELECT 'GB' FROM DUAL UNION ALL
SELECT 'CH' FROM DUAL;

输出:

COMBINATION
GB
GB,CH
CH
CH,GB

然后你可以使用一个简单的CASE表达式将你的字符串输出到组合:

SELECT function_value,
       CASE
       WHEN function_value IN (SELECT SUBSTR(SYS_CONNECT_BY_PATH(value, ','), 2)
                               FROM   static_table
                               CONNECT BY NOCYCLE PRIOR value != value)
       THEN 'false'
       ELSE 'true'
       END AS not_matched
FROM   string_from_function;

其中,对于示例数据:

CREATE TABLE string_from_function(function_value) AS
SELECT 'GB' FROM DUAL UNION ALL
SELECT 'CH' FROM DUAL UNION ALL
SELECT 'GB,CH' FROM DUAL UNION ALL
SELECT 'CH,GB' FROM DUAL UNION ALL
SELECT 'GB,FR' FROM DUAL UNION ALL
SELECT 'FR,ES' FROM DUAL UNION ALL
SELECT 'ES,CH' FROM DUAL UNION ALL
SELECT 'CH,GB,ES' FROM DUAL;

输出:

FUNCTION_VALUE NOT_MATCHED
GB false
CH false
GB,CH false
CH,GB false
GB,FR true
FR,ES true
ES,CH true
CH,GB,ES true

db<>fiddle here