搜索数字是否包含在表达式中:1-3,5,10-15,20

时间:2013-01-22 13:23:39

标签: sql regex oracle

在oracle数据库表中,我需要找到给定批号的结果。 保存批号的字段是包含类似'1-3,5,10-15,20'的字符串(此字符串中的数字已排序)

有没有办法做到这一点?

在上面的例子中,应该找到以下批号的结果:

1,2,3,5,10,11,12,13,14,15,20

在应用程序中无法执行此操作,因此必须在数据库中完成。

类似于:“SELECT * FROM products WHERE lot = 2”

4 个答案:

答案 0 :(得分:7)

可以使用REGEXP_SUBSTR函数和hierarchical queries在SQL中完成所有操作:

with list_of_ids as (
select regexp_substr(a, '[[:digit:]]+',1, 1) as lot1
     , nvl( regexp_substr(a, '(-)([[:digit:]]+)',1, 1, 'i', '2')
          , regexp_substr(a, '[[:digit:]]+',1, 1)) as lot2
  from (select regexp_substr('1-3,5,10-15,20' , '[^,]+', 1, level) as a
          from dual
       connect by regexp_substr('1-3,5,10-15,20' , '[^,]+', 1, level) is not null
               )
       )
select a.*
  from products a
  join list_of_ids b
    on a.lot between b.lot1 and b.lot2

但是,我必须强调,正确规范数据库是最佳选择。这种解决方案可能无法很好地扩展,并且可以完成大量不必要的工作。

它的工作原理如下:

首先在逗号上分割数据:

SQL>  select regexp_substr('1-3,5,10-15,20', '[^,]+', 1, level) as a
  2     from dual
  3  connect by regexp_substr('1-3,5,10-15,20', '[^,]+', 1, level) is not null
  4          ;

A
--------------
1-3
5
10-15
20

接下来,将它拆分为连字符,以便在最终将其连接到表之前提供BETWEEN中使用的最小和最大批次。 NVL用于确保始终存在最大值。

SQL> select regexp_substr(a, '[[:digit:]]+',1, 1) as lot1
  2       , nvl( regexp_substr(a, '(-)([[:digit:]]+)',1, 1, 'i', '2')
  3             , regexp_substr(a, '[[:digit:]]+',1, 1)) as lot2
  4    from (select regexp_substr('1-3,5,10-15,20' , '[^,]+', 1, level) as a
  5            from dual
  6         connect by regexp_substr('1-3,5,10-15,20' , '[^,]+', 1, level) is not null
  7                 )
  8         ;

LOT1           LOT2
-------------- --------------
1              3
5              5
10             15
20             20

SQL>

这是一个有完整查询的SQL Fiddle工作。

答案 1 :(得分:3)

这是PIPELINED FUNCTION的解决方案:

create type array_number
    as table of number
/ 

create or replace function x_tbl(a varchar2) return array_number pipelined  as
token varchar2(10);
str varchar2(1000):=a;
k number;
should_exit boolean;
begin
should_exit := false;
loop
    if instr(str,',') > 0 then 
        token:=substr(str,1,instr(str,',')-1);
    else 
        token := str;
        should_exit:=true;
    end if;
    if instr(token,'-') > 0 then 
        k:=to_number(substr(token, 1, instr(token,'-')-1)) ;
        loop
            pipe row(k);
            k:=k+1;
            exit when k>to_number(substr(token, instr(token,'-')+1)) ;
        end loop;
    else pipe row(token);
    end if;
    --dbms_output.put_line(token);
    --dbms_output.put_line(instr(str,','));
    str:=substr(str, instr(str,',')+1);
    exit when should_exit;
end loop;
end;
/

查询:

select * from table(x_tbl('22-27,33,444-448'));

结果:

22
23
24
25
26
27
33
444
445
446
447
448

所以,你可以:

 select 1 from dual where 23 in (select * from  table(x_tbl('22-27,33,444-448')));

答案 2 :(得分:-1)

这是T-SQL,但不应该难以移植。

DECLARE @ranges NVARCHAR(MAX);
DECLARE @number INT;

SET @ranges = N'1-3,5,10-15,20';
SET @number = 13;

DECLARE @found BIT;
DECLARE @commaIndex INT;
DECLARE @dashIndex INT;
DECLARE @range NVARCHAR(MAX);
DECLARE @rangeStart INT;
DECLARE @rangeEnd INT;

SET @found = 0;

SET @commaIndex = CHARINDEX(',', @ranges);

WHILE (@commaIndex > 0) BEGIN

  SET @range = SUBSTRING(@ranges, 1, @commaIndex - 1);

  SET @dashIndex = CHARINDEX('-', @range);

  IF (@dashIndex > 0) BEGIN

     SET @rangeStart = CAST(SUBSTRING(@range, 1, @dashIndex - 1) AS INT);
     SET @rangeEnd = CAST(SUBSTRING(@range, @dashIndex + 1, LEN(@range) - @dashIndex) AS INT);

  END ELSE BEGIN

     SET @rangeStart = CAST(@range AS INT);
     SET @rangeEnd = @rangeStart;

  END;

  IF ((@rangeStart <= @number) AND (@number <= @rangeEnd)) BEGIN

    SET @found = 1;

    BREAK;

  END;

  SET @ranges = SUBSTRING(@ranges, @commaIndex + 1, LEN(@ranges) - @commaIndex);
  SET @commaIndex = CHARINDEX(',', @ranges);

END;

IF (@found = 1) BEGIN

    PRINT N'Contained.';

END ELSE BEGIN

    PRINT N'Not contained.';

END;

答案 3 :(得分:-4)

如果您必须仅使用SQL,这是最简单的解决方案。如果您使用PL / SQL,还有更多解决方案。但这可能是最有效和最简单的。而不是硬编码的数字,当然会有你的字符串:

SELECT * FROM products WHERE lot IN (Replace('1-3,5,10-15,20', '-', ',') )
/

如果需要,有一种方法可以删除/替换字符串中的更多字符。上面的示例将' - '替换为','仅... ...

问题是如何得到这个:

"SELECT * FROM products WHERE lot = 2..."

不是这个:

1-3,
2,
4
...

明确说明您的问题/示例。