SQL-запрос в Oracle, чтобы найти число в диапазоне диапазонов, которое представлено в столбце varchar - PullRequest
0 голосов
/ 17 января 2011

У меня есть Colors таблица в Oracle, с такими данными, как:

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 представляет некоторые числа и диапазоны чисел.

Я не могу сохранить диапазоны в некоторых других таблицах (например, RangesTable с ColorID, MinVal, MaxVal), но я могу нормализовать это Ranges -колонку в некоторых отношениях (всегда сортировать или перерисовывать отдельные числа в виде диапазонов ("4-4" вместо "4"), или тому подобное).

Проблема: Я ищу способ опроса моего Oracle в соответствии с этим полем, спрашивая его: Какие цвета (или идентификаторы ...) у меня есть, что его диапазоны содержит 5? (ответ - синий и зеленый) или Какой цвет перекрывает диапазон "5-6"? (ответ, опять же, синий [1-9] и зеленый [4.44-5.3 ]).

Как это можно сделать? (Полагаю, Регекс здесь не поможет ...).

Разумно ли написать функцию в БД, способную разбивать эти диапазоны и искать в ней? Любое другое предложение?

Спасибо!

Ответы [ 3 ]

3 голосов
/ 17 января 2011

Вы можете получить диапазоны с помощью этого запроса:

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.

С уважением,
Роб.

1 голос
/ 17 января 2011

Вы можете создать функцию 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; в более ранних версиях вам может потребоваться написать собственную функцию синтаксического анализа строк, например this

0 голосов
/ 19 января 2011

Я сделал это, используя вложенную таблицу 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...