2009-03-22 6 views
3

У меня есть следующий динамический SQL в одном из моих пакетов телКак я могу избавиться от динамического SQL

OPEN ccur for 
    'select c.category 
    from test_category c 
    where c.deptid='||PI_N_Dept || 
    ' and c.category not in ('|| sExcludeCategories ||')'; 

sExcludeCategories будет содержать набор целых чисел, разделенных запятой. Я хотел бы исключить эту динамическую инструкцию SQL. Есть ли какие-нибудь умные способы сделать это?

ответ

6

Я предполагаю, что вы знаете, вы можете связать переменную PI_N_Dept, чтобы удалить эту часть динамического SQL. К сожалению, для вашего предложения IN и sExcludeCategories вы не можете привязать переменную для списка в Oracle (по крайней мере, до 9,2, насколько я знаю)

У вас есть несколько вариантов. Ваше текущее решение является самым простым. Другим решением является изменение процедуры приема нескольких переменных и создание списка операторов И.

'select c.category 
    from test_category c 
    where c.deptid= :PI_N_Dept 
     and c.category <> :sExcludeCategory1 
     and c.category <> :sExcludeCategory2 
     and c.category <> :sExcludeCategory3 

'; 

или иметь фиксированный список значений

'select c.category 
    from test_category c 
    where c.deptid= :PI_N_Dept 
     and c.category not in (:sExcludeCategory1 , :sExcludeCategory2, :sExcludeCategory3)'; 

Вы должны быть осторожными в том случае, если вы только хотите 2 категории. Третий должен быть установлен на какое-то значение, не относящееся к c.category (NB: будьте осторожны и проверяйте нулевые значения здесь)

Другое решение представлено в Ask Tom. Это выглядит довольно просто, хотя я его не тестировал. Он работает, создавая функцию str2tbl(), которая позволяет передавать серию чисел, разделенных запятыми, и создавать «таблицу» через двойную, чтобы сделать IN.

create or replace type myTableType as table of number; 

create or replace function str2tbl(p_str in varchar2) return myTableType 
    as 
    l_str long default p_str || ','; 
    l_n  number; 
    l_data myTableType := myTabletype(); 
    begin 
     loop 
      l_n := instr(l_str, ','); 
      exit when (nvl(l_n,0) = 0); 
      l_data.extend; 
      l_data(l_data.count) := ltrim(rtrim(substr(l_str,1,l_n-1))); 
      l_str := substr(l_str, l_n+1); 
     end loop; 
     return l_data; 
    end; 

Ваш пример будет выглядеть как

'select c.category 
    from test_category c 
    where c.deptid= :PI_N_Dept 
     and c.category not in (select * from INLIST (select cast(str2tbl(:sExcludeCategories ) as mytableType) from dual))'; 

Это будет работать только если sExcludeCategories был список номеров. Вам нужно будет изменить str2tbl для обработки котировок, если они включены в переменную (и вы не можете ее изменить), а также изменить тип myTableType на varchar2(10) или что-то более подходящее.

В целом, если исходный sql не влияет на производительность, то для простоты я оставил бы его как динамический SQL. Гораздо больнее поддерживать боль. В противном случае проверьте str2tbl. Он должен работать в Oracle 8 и выше.

PS: Только для полноты я столкнулся с this nice article on binding vars, который охватывает простые проблемы, например, хотя и не используя переменные для предложений IN.

+0

Большое спасибо за такой отличный ответ. С небольшой настройкой я смогу решить проблему. – Tuxist

+0

В общем, это очень хороший ответ. Однако я категорически не согласен с вашим заявлением о том, чтобы оставить его динамическим SQL. Использование конкатенации строк для создания вашего SQL оставляет вас уязвимыми для атак SQL-инъекций. – chilltemp

0

Я не знаю оракула, но в SQL Server довольно распространено получение «split» udf (like so - только одна из многих доступных версий), которая превращает CSV в столбец значений и присоедините (и т.д.) к нему. Есть ли у pl-sql что-то подобное?

+0

К сожалению, в Oracle SQL или PL/SQL нет токенизатора. – tuinstoel

0
create or replace type numbertype 
as object 
(nr number(20,10)) 
/

create or replace type number_table 
as table of numbertype 
/

create or replace procedure tableselect 
(p_numbers in number_table 
, p_ref_result out sys_refcursor) 
is 
begin 
    open p_ref_result for 
    select * 
    from employees , (select /*+ cardinality(tab 10) */ tab.nr from table(p_numbers) tab) tbnrs 
    where id = tbnrs.nr; 
end; 
/
0

Вы можете написать это так:

OPEN ccur for 
    select c.category 
    from test_category c 
    where 
    c.deptid= PI_N_Dept 
    and c.category not in 
    (select category_id from categories where <some-condition-that-finds-the-categories-that-should-be-excluded>); 
0

Вы можете решить эту проблему путем массового копирования ваших значений во временную таблицу и присоединения к ней. Сначала это может показаться немного излишним, но, поскольку это очень распространенный шаблон, вы, конечно, в конечном итоге снова используете этот код.

+0

Это немного медленнее, потому что вы храните вещи в таблице темпов, которые вам не нужно хранить вообще. – tuinstoel

+0

единичная вставка в временную таблицу не будет очень обременительной. –

Смежные вопросы