Oracle FAQ Your Portal to the Oracle Knowledge Grid
HOME | ASK QUESTION | ADD INFO | SEARCH | E-MAIL US
 

Home -> Community -> Usenet -> c.d.o.misc -> Re: How do I bind variables to dynamic SQL?

Re: How do I bind variables to dynamic SQL?

From: Thomas Kyte <tkyte_at_us.oracle.com>
Date: Mon, 06 Sep 1999 13:52:35 -0400
Message-ID: <C=3TNxWYsvBxg9AAE4PxOnHrOXrA@4ax.com>


On Mon, 6 Sep 1999 11:52:00 +0100, you wrote:

>
>have a look at my web-site; one item describes
>binding variables in PL/SQL blocks using dbms_sql,
>another item (for users of oracle 8) describes using
>object types to pass a list of values as a single parameter.
>
>You SQL then does:
> where colX in (cast varray bind variable into subquery)
>or
> where colX not in (cast varray bind variable into subquery)
>
>
>--
>
>Jonathan Lewis
>Yet another Oracle-related web site: http://www.jlcomp.demon.co.uk
>
>dperez_at_juno_nospam.com wrote in message
><37cf22b7.3496227_at_news.uswest.net>...
>>Um, Thomas, I think you may have missed the most important point in my
>original
>>post:
>>
>> The string of comma-delimited integers MUST remain intact.
>> Parsing it, and creating a varying number of
>> predicates would NOT solve the problem.
>>

Yes it will. As I said (my most important point in my post)

The only way you can do this and support bind variables is to PARSE it (but not create a varying number of predicates) and set some upper limit on the total number of IN list values (which is OK, since we have a hard limit anyway:

If you want bind variables -- you will parse, no ifs ands ors about it -- you will parse -- the integer list MUST be broken out into its individual entries -- it has to be.

Since you never specified "not in" in the original request -- yes, we have to rework this a little. But the fact is -- it can easily be done.

X NOT IN ( a, b, c )

is ALMOST the same as:

NOT EXISTS ( select null from dual where X in ( a, b, c ) )

This difference between the two is that if any of A, B or C is NULL the first predicate never returns TRUE or FALSE (and thats your problem with the original solution) but the second one does (it lets A, B, or C be null and still returns records -- which is the desired effect)

So, we can use a postive IN list (which allows for the extra nulls) and negate it. A modified version of a solution is thus:

tkyte_at_8.0> create or replace procedure dynquery( p_in_list in varchar2,

  2                                        p_use_not in boolean default FALSE )
  3  is
  4      l_tmp_list      varchar2(4096) default p_in_list || ',';
  5      l_n             number;
  6  
  6      l_theCursor     integer default dbms_sql.open_cursor;
  7      l_columnValue   varchar2(2000);
  8      l_status        integer;
  9      l_query         varchar2(1000)
 10                      default 'select empno, ename
 11                                 from emp
 12                               where ';
 13      l_descTbl       dbms_sql.desc_tab;
 14      l_colCnt        number;
 15          l_sep           char(1) default ' ';
 16  
 16      MAX_COLS        number default 7;
 17  begin
 18          if ( p_use_not )
 19          then
 20                  l_query := l_query || ' not exists ( select null from dual
where EMPNO in (';
 21          else
 22                  l_query := l_query || 'empno in (';
 23          end if;
 24  
 24          for i in 1 .. MAX_COLS loop
 25                  l_query := l_query || l_sep || ':i' || i;
 26                  l_sep := ',';
 27          end loop;
 28          if ( p_use_not ) then
 29                  l_query := l_query || '))';
 30          else
 31                  l_query := l_query || ')';
 32          end if;
 33  
 33                  dbms_output.put_line( l_query );
 34      dbms_sql.parse(  l_theCursor,  l_query, dbms_sql.native );
 35  
 35      for i in 1 .. MAX_COLS loop
 36          l_n := instr( l_tmp_list, ',' );
 37                  -- this ends up binding NULL eventually after
 38                  -- we exhaust the in list
 39  
 39          dbms_sql.bind_variable( l_theCursor, ':i' || i,
 40                     to_number(substr( l_tmp_list, 1, l_n-1 )) );
 41          l_tmp_list := substr( l_tmp_list, l_n+1 );
 42      end loop;
 43  
 43      dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );
 44  
 44      for i in 1 .. l_colCnt loop
 45          dbms_sql.define_column( l_theCursor, i, l_columnValue, 2000 );
 46      end loop;
 47  
 47      l_status := dbms_sql.execute(l_theCursor);
 48  
 48      while ( dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
 49          dbms_sql.column_value( l_theCursor, 1, l_columnValue );
 50          dbms_output.put_line( l_columnValue );
 51          dbms_sql.column_value( l_theCursor, 2, l_columnValue );
 52          dbms_output.put_line( l_columnValue );
 53      end loop;

 54 end ;
 55 /

Procedure created.

tkyte_at_8.0>
tkyte_at_8.0> exec dynquery( '7566, 7654, 7698, 7782, 7788' ) select empno, ename

                               from emp
                             where empno
in ( :i1,:i2,:i3,:i4,:i5,:i6,:i7)
7566
JONES
7654
MARTIN
7698
BLAKE
7782
CLARK
7788
SCOTT PL/SQL procedure successfully completed.

tkyte_at_8.0> exec dynquery( '7566, 7654, 7698, 7782, 7788', TRUE ) select empno, ename

                               from emp
                             where  not
exists ( select null from dual where EMPNO in ( :i1,:i2,:i3,:i4,:i5,:i6,:i7)) 7369
SMITH
7499
ALLEN
7521
WARD
7839
KING
7844
TURNER
7876
ADAMS
7900
JAMES
7902
FORD
7934
MILLER PL/SQL procedure successfully completed.

If you use the trick at Jonathan's web site, that'll work as well (but again, you'll be parsing that list of numbers).

>>
>>Parsing the values and retrieiving them one at a time works fine... Been
>there,
>>done that.......... BUT, in the actual program there are ACTUALLY TWO
>>QUERIES...........
>>
>>1: select *... WHERE ... IN (set...)
>>
>>2: select *... WHERE ... NOT IN (set...)
>>
>>Parsing the string into some unknown series of individual values then
>putting
>>each in the select statement would seem not to work well when doing a "not
>>in"....... Thus, the reason for the original statement "THE STRING OF
>>COMMA-DELIMITED INTEGERS MUST REMAIN INTACT."
>>

if we get the question -- not a direction to give a particular (impossible) solution we'll be able to answer them much better.

Your requirement:

NO PARSING plus the desired feature:

USE BIND VARIABLES is simply not possible to achieve. If you had asked instead "given a comma separated list of numbers -- i would like to have a query i could use to let me to an IN using bind variables on that list as well as a query that lets me do NOT IN on that list" -- we could have given you the solutions much faster.

>>I'll give the instr a shot. It may have such serious performance
>implications
>>its not usable, but its worth a shot.
>>

beware the instr:

tkyte_at_8.0> variable object_list varchar2(25)
tkyte_at_8.0> 
tkyte_at_8.0> exec :object_list := '10, 11, 12, 13, 14, 15'

PL/SQL procedure successfully completed.

tkyte_at_8.0>
tkyte_at_8.0> select user_id
  2 from all_users a where
  3 instr( :object_list|| ',', to_char( a.user_id) || ',') <> 0   4 /

   USER_ID


         0
         5

(neither 5 nor 0 was in the in list)

it's tricky to use the instr (gotta send in a string with delimiters around each element else you'll get the wrong answer as above). Its not very efficient if the thing being searched on can be used in the index.   

I'd go with the <not> in list for performance and meaning...

>

--
See http://govt.us.oracle.com/~tkyte/ for my columns 'Digging-in to Oracle8i'... Current article is "Part I of V, Autonomous Transactions" updated June 21'st  

Thomas Kyte                   tkyte_at_us.oracle.com
Oracle Service Industries     Reston, VA   USA

Opinions are mine and do not necessarily reflect those of Oracle Corporation Received on Mon Sep 06 1999 - 12:52:35 CDT

Original text of this message

HOME | ASK QUESTION | ADD INFO | SEARCH | E-MAIL US