在oracle数据库表中,我需要找到给定批号的结果。 保存批号的字段是包含类似'1-3,5,10-15,20'的字符串(此字符串中的数字已排序)
有没有办法做到这一点?
在上面的例子中,应该找到以下批号的结果:
1,2,3,5,10,11,12,13,14,15,20
在应用程序中无法执行此操作,因此必须在数据库中完成。
类似于:“SELECT * FROM products WHERE lot = 2”
答案 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
...
明确说明您的问题/示例。