如何匹配Oracle中2个列表中的值?

时间:2018-12-12 11:09:01

标签: sql oracle oracle12c

我想创建一个基于过滤器的varchar列,并且该值可以具有一个或多个逗号分隔的字符串,例如

  col
a 12
b 489
c 456,486
d 489,45,789

如果条目为489,则返回值应为b and d 如果条目为45,489,则返回值应为d 无论值顺序如何,此列都是外键列。 如何在oracle中做到这一点?

3 个答案:

答案 0 :(得分:2)

撇开数据模型的问题,如果您必须比较两个CSV字符串的内容,而忽略元素出现的顺序,则需要至少将其中的一个拆分为各个组成部分。

此方法基于@Tim的like比较,方法是将用户提供的值拆分为CTE中的单独值,使用like机制将其连接到实际表中,然后计算那里有多少个匹配项是。

with cte (val, cnt) as (
  select regexp_substr('45,489', '(.*?)(,|$)', 1, level, null, 1),
    regexp_count('45,489', ',') + 1
  from dual
  connect by level <= regexp_count('45,489', ',') + 1
)
select t.id, t.col
from your_table t
join cte on ',' || t.col || ',' like '%,' || cte.val || ',%'
group by t.id, t.col
having count(cte.val) = max(cte.cnt)

仅返回连接值数量与连接值计数匹配的行。

在另一个CTE中使用示例数据进行快速演示:

with your_table (id, col) as (
            select 'a', '12' from dual
  union all select 'b', '489' from dual
  union all select 'c', '456,486' from dual
  union all select 'd', '489,45,789' from dual
  union all select 'e', '1,489,2,45,3' from dual
  union all select 'f', '1,489,2,45,3,489' from dual
),
cte (val, cnt) as (
  select regexp_substr('45,489', '(.*?)(,|$)', 1, level, null, 1),
    regexp_count('45,489', ',') + 1
  from dual
  connect by level <= regexp_count('45,489', ',') + 1
)
select t.id, t.col
from your_table t
join cte on ',' || t.col || ',' like '%,' || cte.val || ',%'
group by t.id, t.col
having count(cte.val) = max(cte.cnt);

ID COL                
-- -------------------
f  1,489,2,45,3,489,45
d  489,45,789         
e  1,489,2,45,3       

答案 1 :(得分:1)

您可以使用LIKE以及一些串联技巧:

SELECT id
FROM yourTable
WHERE ',' || id || ',' LIKE '%,489,%';

但是您当前的设计严重次优,因为您将逗号分隔的值存储在id列中。这极大地限制了Oracle筛选该列的能力。相反,您应该尝试将每个id值放到单独的行中。

注意:此答案仅适用于针对id CSV搜索单个值。如果需要同时搜索多个值,则必须复制WHERE子句中的逻辑。

答案 2 :(得分:1)

将值存储为嵌套表,而不是将值存储为字符串,您可以使用SUBMULTISET运算符:

Oracle设置

CREATE TYPE intlist IS TABLE OF NUMBER(3,0)
/

CREATE TABLE table_name (
  name CHAR(1) PRIMARY KEY,
  ids intlist
) NESTED TABLE ids STORE AS table_name__ids;

INSERT INTO table_name ( name, ids )
SELECT 'a', intlist( 12 ) FROM DUAL UNION ALL
SELECT 'b', intlist( 489 ) FROM DUAL UNION ALL
SELECT 'c', intlist( 456, 486 ) FROM DUAL UNION ALL
SELECT 'd', intlist( 489, 45, 789 ) FROM DUAL;

查询1

SELECT name
FROM   table_name
WHERE  intlist( 489 ) SUBMULTISET OF ids;

输出

NAME
----
b
d

查询2

SELECT name
FROM   table_name
WHERE  intlist( 45, 489 ) SUBMULTISET OF ids;

输出

NAME
----
d

如果必须将值存储为字符串,则可以创建一个函数以将字符串转换为集合并使用相同的技术:

Oracle设置

CREATE TYPE intlist IS TABLE OF NUMBER(3,0)
/

CREATE OR REPLACE FUNCTION splitNumberList(
  i_str    IN  VARCHAR2,
  i_delim  IN  VARCHAR2 DEFAULT ','
) RETURN INTLIST DETERMINISTIC
AS
  p_result       INTLIST := INTLIST();
  p_start        NUMBER(5) := 1;
  p_end          NUMBER(5);
  c_len CONSTANT NUMBER(5) := LENGTH( i_str );
  c_ld  CONSTANT NUMBER(5) := LENGTH( i_delim );
BEGIN
  IF c_len > 0 THEN
    p_end := INSTR( i_str, i_delim, p_start );
    WHILE p_end > 0 LOOP
      p_result.EXTEND;
      p_result( p_result.COUNT ) := TO_NUMBER( SUBSTR( i_str, p_start, p_end - p_start ) );
      p_start := p_end + c_ld;
      p_end := INSTR( i_str, i_delim, p_start );
    END LOOP;
    IF p_start <= c_len + 1 THEN
      p_result.EXTEND;
      p_result( p_result.COUNT ) := TO_NUMBER( SUBSTR( i_str, p_start, c_len - p_start + 1 ) );
    END IF;
  END IF;
  RETURN p_result;
END;
/

CREATE TABLE table_name (
  name CHAR(1) PRIMARY KEY,
  ids  VARCHAR2(4000)
);

INSERT INTO table_name ( name, ids )
SELECT 'a', '12' FROM DUAL UNION ALL
SELECT 'b', '489' FROM DUAL UNION ALL
SELECT 'c', '456,486' FROM DUAL UNION ALL
SELECT 'd', '489,45,789' FROM DUAL;

查询3

SELECT name
FROM   table_name
WHERE  splitNumberList( '489' ) SUBMULTISET OF splitNumberList( ids );

输出

NAME
----
b
d

查询4

SELECT name
FROM   table_name
WHERE  splitNumberList( '45,489' ) SUBMULTISET OF splitNumberList( ids );

输出

NAME
----
d