Oracle 中的 SQL 查询查找 varchar 列中表示的范围字段内的数字

发布于 2024-10-12 09:17:07 字数 819 浏览 3 评论 0原文

我在 Oracle 中有 Colors 表,其中包含如下数据:

ID    Color    Ranges (nvarchar2!)
--    -----    -------------------------
1     Blue     1-9,23.5-25.1,27.11,99.14
2     Red      4
3     Green    4.44-5.3
4     Black    18-22,101

正如您所猜测的,Ranges 列代表一些数字和数字范围。

无法将范围保存在其他一些表中(例如带有 ColorIDMinValRangesTable MaxVal),但我可以以某种方式标准化此范围列(始终排序,或者将单个数字表示为范围(“4 -4”而不是“4”),等等)。

问题:我正在寻找一种根据此字段查询 Oracle 的方法,通过询问它:我有哪些颜色(或 ids...),其范围包含 5 ?(答案是蓝色和绿色),或者哪种颜色与“5-6”范围重叠?(答案同样是蓝色 [1-9] 和绿色 [4.44-5.3] ])。

怎么办呢? (我想正则表达式在这里没有帮助......)。

编写能够分割这些范围并在其中搜索的数据库内函数是否明智?还有其他建议吗?

谢谢你!

I have Colors table in Oracle, with data like:

ID    Color    Ranges (nvarchar2!)
--    -----    -------------------------
1     Blue     1-9,23.5-25.1,27.11,99.14
2     Red      4
3     Green    4.44-5.3
4     Black    18-22,101

As you can guess, the Ranges column represents some numbers and numbers-ranges.

I can't save the ranges in some other tables (like RangesTable with ColorID, MinVal, MaxVal), but I can normalize this Ranges-column in some ways (allways sorted, or, represting single-numbers as ranges ("4-4" instead of "4"), or such).

The problem: I'm searching for a way to query my Oracle according to this field, by asking it: Which colors (or ids...) do I have that its ranges contains 5? (the answer is Blue and Green), or Which color overlaps "5-6" range? (answer is, again, Blue [1-9] and Green [4.44-5.3]).

How can it be done? (I guess Regex won't help here...).

Is it sensable to write in-DB function that capable to split those ranges and search in it? Any other suggestion?

Thank you!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

无所的.畏惧 2024-10-19 09:17:07

您可以通过此查询获取您的范围:

SQL> select id
  2         , color
  3         , to_number(case when ranges like '%-%' then regexp_substr(ranges,'[^-]+',1,1) else ranges end) low_value
  4         , to_number(case when ranges like '%-%' then regexp_substr(ranges,'[^-]+',1,2) else ranges end) high_value
  5      from colors
  6     model
  7           return updated rows
  8           partition by (id,color)
  9           dimension by (0 i)
 10           measures (ranges,nvl(length(regexp_replace(ranges,'[^,]')),0) + nvl2(ranges,1,0) as number_of_parts)
 11           ( ranges[for i from 1 to number_of_parts[0] increment 1]
 12             = regexp_substr(ranges[0],'[^,]+',1,cv(i))
 13           )
 14  /

                  ID COLOR            LOW_VALUE           HIGH_VALUE
-------------------- ----- -------------------- --------------------
                   2 Red                      4                    4
                   1 Blue                     1                    9
                   1 Blue                  23.5                 25.1
                   1 Blue                 27.11                27.11
                   1 Blue                 99.14                99.14
                   4 Black                   18                   22
                   4 Black                  101                  101
                   3 Green                 4.44                  5.3

8 rows selected.

问候,
抢。

You can get your ranges with this query:

SQL> select id
  2         , color
  3         , to_number(case when ranges like '%-%' then regexp_substr(ranges,'[^-]+',1,1) else ranges end) low_value
  4         , to_number(case when ranges like '%-%' then regexp_substr(ranges,'[^-]+',1,2) else ranges end) high_value
  5      from colors
  6     model
  7           return updated rows
  8           partition by (id,color)
  9           dimension by (0 i)
 10           measures (ranges,nvl(length(regexp_replace(ranges,'[^,]')),0) + nvl2(ranges,1,0) as number_of_parts)
 11           ( ranges[for i from 1 to number_of_parts[0] increment 1]
 12             = regexp_substr(ranges[0],'[^,]+',1,cv(i))
 13           )
 14  /

                  ID COLOR            LOW_VALUE           HIGH_VALUE
-------------------- ----- -------------------- --------------------
                   2 Red                      4                    4
                   1 Blue                     1                    9
                   1 Blue                  23.5                 25.1
                   1 Blue                 27.11                27.11
                   1 Blue                 99.14                99.14
                   4 Black                   18                   22
                   4 Black                  101                  101
                   3 Green                 4.44                  5.3

8 rows selected.

Regards,
Rob.

许你一世情深 2024-10-19 09:17:07

您可以创建一个像这样的 PL/SQL 函数:

function value_included (p_value in number, p_ranges in varchar2)
return number
is
    l_ranges_tab apex_application_global.vc_arr2;
    l_values_tab apex_application_global.vc_arr2;
    l_retval number := 0;
begin
    l_ranges_tab := apex_util.string_to_table (p_ranges, ',');
    for i in 1..l_ranges_tab.count loop
         l_values_tab := apex_util.string_to_table (l_ranges_tab(i), '-');
         if l_values_tab.count = 1 then
             if p_value = l_values_tab(1) then
                 l_retval := 1;
                 exit;
             end if;
         else
             if p_value between l_values_tab(1) and l_values_tab(2) then
                 l_retval := 1;
                 exit;
             end if;
         end if;
    end loop;
    return l_retval;
end;

如果值包含在范围内,则返回 1,如果不包含,则返回 0,并且可以像这样使用:

select color from colors where value_included(5, ranges);

可以编写类似的函数来处理重叠范围:

function range_overlap (p_from in number, p_to in number, p_ranges in varchar2)
return number
is
    l_ranges_tab apex_application_global.vc_arr2;
    l_values_tab apex_application_global.vc_arr2;
    l_retval number := 0;
begin
    l_ranges_tab := apex_util.string_to_table (p_ranges, ',');
    for i in 1..l_ranges_tab.count loop
         l_values_tab := apex_util.string_to_table (l_ranges_tab(i), '-');
         if l_values_tab.count = 1 then
             if l_values_tab(1) between p_from and p_to then
                 l_retval := 1;
                 exit;
             end if;
         else
             if p_to >= l_values_tab(1) and p_from <= l_values_tab(2) then
                 l_retval := 1;
                 exit;
             end if;
         end if;
    end loop;
    return l_retval;
end;

注意: apex_util.string_to_table 函数在最新版本的 Oracle 中作为标准提供;在早期版本中,您可能需要编写自己的字符串解析器函数,例如 这个

You could create a PL/SQL function like this:

function value_included (p_value in number, p_ranges in varchar2)
return number
is
    l_ranges_tab apex_application_global.vc_arr2;
    l_values_tab apex_application_global.vc_arr2;
    l_retval number := 0;
begin
    l_ranges_tab := apex_util.string_to_table (p_ranges, ',');
    for i in 1..l_ranges_tab.count loop
         l_values_tab := apex_util.string_to_table (l_ranges_tab(i), '-');
         if l_values_tab.count = 1 then
             if p_value = l_values_tab(1) then
                 l_retval := 1;
                 exit;
             end if;
         else
             if p_value between l_values_tab(1) and l_values_tab(2) then
                 l_retval := 1;
                 exit;
             end if;
         end if;
    end loop;
    return l_retval;
end;

This returns 1 if the value is included in the range(s), 0 if not and can be used like this:

select color from colors where value_included(5, ranges);

A similar function could be written to handle overlapping ranges:

function range_overlap (p_from in number, p_to in number, p_ranges in varchar2)
return number
is
    l_ranges_tab apex_application_global.vc_arr2;
    l_values_tab apex_application_global.vc_arr2;
    l_retval number := 0;
begin
    l_ranges_tab := apex_util.string_to_table (p_ranges, ',');
    for i in 1..l_ranges_tab.count loop
         l_values_tab := apex_util.string_to_table (l_ranges_tab(i), '-');
         if l_values_tab.count = 1 then
             if l_values_tab(1) between p_from and p_to then
                 l_retval := 1;
                 exit;
             end if;
         else
             if p_to >= l_values_tab(1) and p_from <= l_values_tab(2) then
                 l_retval := 1;
                 exit;
             end if;
         end if;
    end loop;
    return l_retval;
end;

Note: the apex_util.string_to_table function is available as standard in recent versions of Oracle; in earlier versions you may need to write your own string parser function like this one

尬尬 2024-10-19 09:17:07

我通过使用 SQL 嵌套表和管道函数来提供第三个选项来完成此操作。

首先创建SQL类型和相关的嵌套表:

create or replace type range_type as object (range_from number, range_to number);
create or replace type range_table as table of range_type;

然后创建一个可以解码范围列的管道函数。该函数可能可以轻松地重写以利用上面使用的 apex_util.string_to_table 函数。

create or replace function range_to_nested_table(i_ranges in varchar2) 
  return range_table pipelined is

  thisRange varchar2(4000);
  loop_counter number := 1;

  output_row range_type;

begin

  loop
    thisRange := rtrim(regexp_substr(i_ranges, '[^,]*,?', 1, loop_counter), ',');

    exit when thisRange is null;
    loop_counter := loop_counter + 1;

    if thisRange like '%-%' then 
      output_row := range_type(to_number(regexp_substr(thisRange, '[^-]*', 1, 1)), 
                               to_number(regexp_substr(thisRange, '[^-]*(-|$)', 1, 2)));
    else
      output_row := range_type(to_number(thisRange), to_number(thisRange));
    end if;

    pipe row(output_row);

  end loop;

  RETURN;

end;

然后运行以下查询来检索数据:

with my_sample_data as (
  select 1 as id, 'Blue' as color, '1-9,23.5-25.1,27.11,99.14' as range from dual union all
  select 2 as id, 'Red' as color, '4' as range from dual union all
  select 3 as id, 'Green' as color, '4.44-5.3' as range from dual union all
  select 4 as id, 'Black' as color, '18-22,101' as range from dual
)
select id, color, range, b.*
from my_sample_data a, table(range_to_nested_table(a.range)) b
where 5 between b.range_from and b.range_to

I have done this by using a SQL nested table and a pipelined function to provide a third option.

Firstly create the SQL type and related nested table:

create or replace type range_type as object (range_from number, range_to number);
create or replace type range_table as table of range_type;

Then create a pipelined function that can decode the range column. The function can probably easily be rewritten to take advantage of the apex_util.string_to_table function used above.

create or replace function range_to_nested_table(i_ranges in varchar2) 
  return range_table pipelined is

  thisRange varchar2(4000);
  loop_counter number := 1;

  output_row range_type;

begin

  loop
    thisRange := rtrim(regexp_substr(i_ranges, '[^,]*,?', 1, loop_counter), ',');

    exit when thisRange is null;
    loop_counter := loop_counter + 1;

    if thisRange like '%-%' then 
      output_row := range_type(to_number(regexp_substr(thisRange, '[^-]*', 1, 1)), 
                               to_number(regexp_substr(thisRange, '[^-]*(-|$)', 1, 2)));
    else
      output_row := range_type(to_number(thisRange), to_number(thisRange));
    end if;

    pipe row(output_row);

  end loop;

  RETURN;

end;

Then run the following query to retrieve the data:

with my_sample_data as (
  select 1 as id, 'Blue' as color, '1-9,23.5-25.1,27.11,99.14' as range from dual union all
  select 2 as id, 'Red' as color, '4' as range from dual union all
  select 3 as id, 'Green' as color, '4.44-5.3' as range from dual union all
  select 4 as id, 'Black' as color, '18-22,101' as range from dual
)
select id, color, range, b.*
from my_sample_data a, table(range_to_nested_table(a.range)) b
where 5 between b.range_from and b.range_to
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文