Динамически добавлять предложения where к курсору в oracle - PullRequest
6 голосов
/ 18 декабря 2008

У меня есть процедура plsql, которая принимает определенные параметры, например, v_name, v_country, v_type.

Я хотел бы иметь курсор с оператором select, подобным этому:

select column from table1 t1, table2 t2
where t1.name = v_name
and t1.country = v_country
and t1.id = t2.id
and t2.type = v_type

Если некоторые параметры пусты, я могу только добавить соответствующие предложения where к курсору? Или есть лучший способ сделать это?

Ответы [ 5 ]

5 голосов
/ 18 декабря 2008

Лучший способ использовать это с DBMS_SQL.

Вы создаете строку, которая представляет ваш оператор SQL. Вы все еще используете переменные связывания. Это больно.

Это выглядит примерно так (я не скомпилировал это, но должно быть близко): -

CREATE OR REPLACE FUNCTION find_country( v_name  t1.country%TYPE,
                                         v_type  t2.type%TYPE)  /* Hmm, column called type? */
DECLARE
  v_SQL         varchar2(2000);
  v_select          INTEGER;   /* "Pointer" to a DBMS_SQL select statement */
  v_execute         INTEGER;

BEGIN
  v_SQL := 'select column from table1 t1, table2 t2 ||
           'where t1.id = t2.id';

  IF v_name IS NOT NULL THEN
    v_SQL := v_SQL || ' AND t1.country = :v_name'
  END IF;

  IF v_type IS NOT NULL THEN
    v_SQL := v_SQL || ' AND t2.type = :v_type';
  END IF;

  /* Setup Cursor */
  v_select := dbms_sql.open_cursor;     
  dbms_sql.parse( v_select, v_SQL, DBMS_SQL.native);

  IF v_name IS NOT NULL THEN
    dbms_sql.bind_variable( v_select, ':v_name', v_name );
  END IF;

  IF v_type IS NOT NULL THEN
    dbms_sql.bind_variable( v_select, ':v_type', v_type );
  END IF;

  DBMS_SQL.DEFINE_COLUMN(v_select, 1, v_column);  /* This is what we have selected */

  /* Return value from EXECUTE is undefined for a SELECT */     
  v_execute := DBMS_SQL.EXECUTE( v_select );

  IF DBMS_SQL.FETCH_ROWS( v_select ) > 0 THEN

    /* A row was found  
    DBMS_SQL.COLUMN_VALUE( v_select, 1, v_column);

    /* Tidy Up */
    DBMS_SQL.CLOSE_CURSOR(v_select);

    RETURN v_ID_address;

  ELSE

     DBMS_SQL.CLOSE_CURSOR(v_select);

     /* No row */
     RETURN NULL;
  END IF;

  EXCEPTION
    WHEN OTHERS THEN
      IF DBMS_SQL.IS_open(v_select) THEN
        DBMS_SQL.CLOSE_CURSOR(v_select);
      END IF;
      RAISE;
END;

Этот подход настолько болезнен по сравнению с простым написанием встроенного SQL, что, если у вас нет кучи столбцов, иногда проще написать пару разных версий с использованием этого синтаксиса:

FOR r IN (SELECT blah FROM blah WHERE t1 = v_t1) LOOP
   func( r.blah );
END LOOP;
3 голосов
/ 18 декабря 2008

Это не совсем то, о чем вы просите, но это может быть приемлемым решением:

select column from table1 t1, table2 t2
where
    (v_name is null or t1.name = v_name)
and (v_country is null or t1.country = v_country)
and t1.id = t2.id
and (v_type is null or t2.type = v_type)
3 голосов
/ 18 декабря 2008

Один из способов - создать запрос в виде строки, а затем использовать выполнить немедленно

2 голосов
/ 15 января 2009

Вам не нужно использовать dbms_sql для решения этой проблемы и вы все еще можете использовать обычный курсор, используя курсор ref.

Пример:

DECLARE
  TYPE cursor_ref IS REF CURSOR;
  c1 cursor_ref;
  r1 table1.column%type;
BEGIN
  l_sql := 'select t1.column from table1 t1, table2 t2 where t1.id = t2.id ';
  if v_name is not null then
    l_sql := l_sql||' and t1.name = '||v_name ;
  end if;
  if v_country is not null then
    l_sql := l_sql||' and t1.country = '||v_country';
  end if;
  if v_type is not null then  
    l_sql := l_sql||' and t2.type = '||v_type';
  end if;
  open c1 for l_sql;
  loop
      fetch c1 into r1;
      exit when c1%notfound;
      -- do something
  end loop;
  close c1;
end;
/

Вы можете сделать это лучше, связав переменные с помощью команды 'using', например так:

open c1 for l_sql using v_name, v_country;
2 голосов
/ 02 января 2009

лучший способ сделать это - использовать функцию Oracle Application Context, лучше всего определяемую как лучшая производительность и безопасность.

Способ быстрее был бы тем, что предложил hamishmcn, используя EXECUTE IMMEDIATE. Я бы выбрал это вместо предложения WW DBMS_SQL каждый раз .

Другой способ, который быстрее всего написать , но не будет работать так же хорошо, будет выглядеть примерно так:

select column from table1 t1, table2 t2
where t1.name = nvl(v_name, t1.name)
and t1.country = nvl(v_country, t1.country)
and t1.id = t2.id
and t2.type = nvl(v_type, t2.type)
...