Помогите с алгоритмом :-(

DVLev
Дата: 08.06.2006 17:57:58
Есть входная строка: "123-145, 45, 788-987" - фактически это ID документов, которые необходимо показать пользователю. Т.е. в данном случае SQL выглядит так:

SELECT id, name FROM table_name WHERE 
id BETWEEN 123 AND 145 
   OR
id = 45
   OR
id BETWEEN 788 AND 987

Необходимо динамически сформировать секцию WHERE

На PHP (на нем первоначально было написано приложение) все довольно просто:

$in["printnumbers"] = '123-145, 45, 788-987';
		if($in["printnumbers"]){
			$prex = explode(",",$in["printnumbers"]);
			foreach($prex as $v){
				$v = trim($v);
				if(eregi("([0-9]+) ?- ?([0-9]+)", $v, $sep)){
					$prexout[] = 'id BETWEEN '.$sep[1]." AND ".$sep[2]." ";
				}elseif(eregi("([0-9]+)", $v, $sep)){
					$prexout[] = "id = '".$sep[1]."' ";
				}
			}
			$addstr = implode(" OR ", $prexout);
		}

На oracle столкнулся с недостатком знаний и непониманием где что собственно искать. Получилось что-то вроде этого.

  PROCEDURE getPrintNumbers(pr IN VARCHAR2) IS
    CURSOR get_words(pr VARCHAR2, shb VARCHAR2) IS
      SELECT REGEXP_SUBSTR(pr, shb, 1, LEVEL, 'm') words
        FROM dual
      CONNECT BY REGEXP_SUBSTR(pr, shb, 1, LEVEL, 'm') IS NOT NULL;
    TYPE wordstype IS TABLE OF VARCHAR2(100) INDEX BY BINARY_INTEGER;
    words  WORDSTYPE;
    i      INTEGER := 0;
  BEGIN
    OPEN get_words(pr, '[^,]+');
    LOOP
      i := i + 1;
      FETCH get_words
        INTO words(i);
      EXIT WHEN get_words%NOTFOUND;
    END LOOP;
    CLOSE get_words;
   /* а дальше полный тупняк :-((( */
  END;


Как все это хозяйство дальше распарсивать?

1. Есть ли какие-либо пакеты для работы с массивами и строками?
2. Как в PL/SQL работать с регулярными выражениями, т.е. чтобы функция возвращала булево значение в зависимости от того нашла она патерн в строке или нет? Типа
IF dbms_regexp.is_contain(...) THEN
или все это надо сначала реализовать собственными силами?
3. Есть ли пакет в oracle для работы со строками и регулярными выраженями?

Кроме этого, сдается мне, все можно реализовать гораздо красивее, "Истина где то рядом (с)"

Заранее спасибо.
Ловец Стрекоз
Дата: 08.06.2006 18:36:43
ключевое слово: REGEXP_REPLACE
iV@n
Дата: 08.06.2006 18:44:57
можно так:
SQL> select u.rn from
  2  (select rownum rn from user_objects where rownum <= 1000) u,
  3  (select decode(substr(s,1,instr(s,'-')-1),'',s,substr(s,1,instr(s,'-')-1)) b, substr(s,instr(s,'-') + 1) e from (
  4  select substr(s,instr(s,',',p.r) + 1,instr(s,',',p.r+1) - instr(s,',',p.r) - 1) s from
  5  (select ','||'123-145,45,788-987'||',' s from dual) d, (select rownum r from user_objects where rownum <= 100) p)
  6  where s is not null) i
  7  where u.rn >= i.b and u.rn <= i.e
  8  order by rn
  9  /

        RN
----------
        45
       123
       124
       125
       ....
или так:
SQL> select 'id'|| case when b=e then '='||b else ' between '||b||' and '||e end q from (
  2  select decode(substr(s,1,instr(s,'-')-1),'',s,substr(s,1,instr(s,'-')-1)) b, substr(s,instr(s,'-') + 1) e from (
  3  select substr(s,instr(s,',',p.r) + 1,instr(s,',',p.r+1) - instr(s,',',p.r) - 1) s from
  4  (select ','||'123-145,45,788-987'||',' s from dual) d, (select rownum r from user_objects where rownum <= 100) p)
  5  where s is not null)
  6  /

Q
--------------------------------------------------------
id between 123 and 145
id=45
id between 788 and 987

только без регулярных выражений за неимением последних :(
DVLev
Дата: 08.06.2006 18:55:36
2Ловец Стрекоз: уже копаю, думаю в самый раз

2Iv@n: я снимаю перед Вами шляпу и понимаю что мне еще многому предстоит научиться. Вот это действительно ВЛАДЕНИЕ SQL !
Denis Popov
Дата: 08.06.2006 19:42:13
with x as (select '123-145, 45, 788-987' code from dual)
select replace (substr(code, 6), ',', null) code
from (
  select regexp_replace (
             regexp_replace (
                ','||replace(x.code, ' ', null)||','
              , '(\d*)-(\d*)'
              , ' or id between \1 and \2'
             )
           , '(,\d*,)'
           , ' or id = \1'
         ) code
  from x
);
M_IV
Дата: 08.06.2006 20:08:27
Denis Popov
with x as (select '123-145, 45, 788-987' code from dual)
select replace (substr(code, 6), ',', null) code
from (
  select regexp_replace (
             regexp_replace (
                ','||replace(x.code, ' ', null)||','
              , '(\d*)-(\d*)'
              , ' or id between \1 and \2'
             )
           , '(,\d*,)'
           , ' or id = \1'
         ) code
  from x
);


Vse zamechatel'no, no kazhetsja, skobok ne hvataet
id between 123 and 145 or id = 45 or id between 788 and 987
ruska_guest
Дата: 24.08.2006 08:00:18
Привет! По-моему, так лучшЕЕ, чем генерить "where".
-----------------------------------------------------
-- Export file for user R3                         --
-- Created by Администратор on 24.08.2006, 9:56:22 --
-----------------------------------------------------

create or replace type tNumbersTable as table of number
/

create or replace package test111 is

  type tcursor is ref cursor;
  function str2t(s in varchar2) return tnumberstable;

end;
/

create or replace package body test111 is

  function cnt(in_str in varchar2, in_char in varchar2 := ';', to_split in pls_integer := 0) return pls_integer is
    posb pls_integer := 1;
    posi pls_integer;
    cnt pls_integer := 0;
    str varchar2(32765) := in_str;
    len pls_integer := length(in_char);
  begin
    if to_split = 1 then
      str := ltrim(rtrim(str, in_char), in_char) || in_char;
    end if;
    loop
      posi := instr(str, in_char, posb);
      exit when posi < posb or cnt > 200;
      cnt := cnt + 1;
      posb := posi + len;
    end loop;
    return cnt;
  end;

  function split(in_str in varchar2, in_num in number, in_char in varchar2 := ';') return varchar2 is
    str varchar2(32767) := in_str;
    pos_b integer;
    pos_e integer;
    retval varchar2(32767);
  begin
    str := in_char || rtrim(ltrim(in_str, in_char), in_char) || in_char;
    pos_b := instr(str, in_char, 1, in_num) + length(in_char) - 1;
    pos_e := instr(str, in_char, 1, in_num + 1);
    retval := substr(str, pos_b + 1, pos_e - pos_b - 1);
    if retval is null then
      return '';
    else
      return retval;
    end if;
  exception
    when others then
      return '';
  end split;

  function str2t(s in varchar2) return tnumberstable is
    tn tnumberstable := tnumberstable();
    sub_str varchar2(100);
    n_start number;
    n_end number;
  
    procedure add(num in number) is
    begin
      tn.extend;
      tn(tn.count) := num;
    end;
  
  begin
    for i in 1 .. cnt(s, ',', 1) loop
      sub_str := split(s, i, ',');
      if instr(sub_str, '-') = 0 then
        add(sub_str);
      else
        n_start := split(sub_str, 1, '-');
        n_end := split(sub_str, 2, '-');
        for j in 0 .. (n_end - n_start) loop
          add(n_start + j);
        end loop;
      end if;
    end loop;
    return tn;
  
  end;

end;
/


несколько громоздко, вообще у меня функции split и cnt живут в отдельном пакете

select doc_id, doc_name
  from (select rownum as doc_id, object_name as doc_name from all_objects) docs
  join (select column_value from table(cast(test111.str2t('123-145, 45, 788-987') as tnumberstable))) srch on (docs.doc_id =
                                                                                                             srch.column_value);