在 oracle 数据库表中,我需要找到给定批号的结果。保存批号的字段是一个包含类似“1-3,5,10-15,20”的字符串(此字符串中的数字已排序)
有没有办法做到这一点?
在上面的示例中,应为以下批号找到结果:
1,2,3,5,10,11,12,13,14,15,20
在应用程序中没有办法做到这一点,所以它必须在数据库中完成。
类似:“SELECT * FROM products WHERE lot = 2”
可以通过使用REGEXP_SUBSTR函数和分层查询在 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 。
这是一个具有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')));
这是 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;
如果您必须只使用 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
...
清楚地陈述你的问题/例子。