4

I have a table where I am storing certain conditions along with input parameters as shown below:

CONDITION                        |   INPUT_PARAMS
---------------------------------------------------------
:p_end_date < :p_start_date      |  v_end_date, IN v_start_date
:p_joining_day = 'MONDAY'        |  v_joining_day 

I want to use execute immediate to evaluate the conditions.

select condition, input_param 
into v_execute_condition, v_input_param 
From table;

v_execute_statement  := 
   'IF '||v_execute_condition ||' '||
   'THEN :o_flag := ''Y'';'   ||' '|| 
   'ELSE :o_flag := ''N'';'   ||' '|| 
   'END IF;';

v_execute_statement := 'BEGIN '||v_execute_statement||' END;';

dbms_output.put_line(v_execute_statement);

EXECUTE IMMEDIATE v_execute_statement USING IN input_param OUT v_flag;

This gives me an error. If I do not pass input parameters dynamically it works.

How can I pass the list of input parameters dynamically?

Alex Poole
  • 183,384
  • 11
  • 179
  • 318
user10060883
  • 97
  • 1
  • 10

1 Answers1

4

You can't supply a string list of bind values as a using parameter, so the only way I can see to do this is with nested dynamic SQL calls, which is a bit messy, and means having to declare (and bind) all possible parameters in the inner. nested, dynamic statement.

declare
  v_execute_statement varchar2(4000);
  v_flag varchar2(1);
  v_start_date date := date '2018-01-01';
  v_end_date date := date '2018-01-31';
  v_joining_day varchar2(9) := 'MONDAY';
begin
  -- loop over all rows for demo
  for rec in (
    select condition, input_params
    From your_table
  )
  loop
    v_execute_statement := q'[
      DECLARE
        v_start_date date := :v_start_date;
        v_end_date date := :v_end_date;
        v_joining_day varchar2(9) := :v_joining_day;
      BEGIN 
        EXECUTE IMMEDIATE q'^
          BEGIN
            IF ]' || rec.condition || q'[ THEN
              :o_flag := 'Y';
            ELSE
              :o_flag := 'N';
            END IF;
          END;^'
        USING ]' || rec.input_params || q'[, OUT :v_flag;
      END;]';

    dbms_output.put_line('Statement: ' || v_execute_statement);

    EXECUTE IMMEDIATE v_execute_statement
    USING v_start_date, v_end_date, v_joining_day, OUT v_flag;

    dbms_output.put_line('Result flag: ' || v_flag);
  end loop;
end;
/

I've used the alternative quoting mechanism here to reduce confusion from escaped single quotes. There are two nested levels of quoting - the outer one delimited by q'[...]' and the inner one delimited by q'^...^', but you can use other characters if those are a problem because of your actual table contents. Escaping those quotes for two levels would be quite ugly and hard to follow/get right; and you'd also have to worry about further escaping quotes in your condition strings, which would already be a problem with your existing code for the second sample you provided as that contains a text literal within it.

With your two sample table rows and the dummy date/day values I showed above the output from running that is:

Statement: 
      DECLARE
        v_start_date date := :v_start_date;
        v_end_date date := :v_end_date;
        v_joining_day varchar2(9) := :v_joining_day;
      BEGIN 
        EXECUTE IMMEDIATE q'^
          BEGIN
            IF :p_end_date < :p_start_date THEN
              :o_flag := 'Y';
            ELSE
              :o_flag := 'N';
            END IF;
          END;^'
        USING v_end_date, IN v_start_date, OUT :o_flag;
      END;
Result flag: N
Statement: 
      DECLARE
        v_start_date date := :v_start_date;
        v_end_date date := :v_end_date;
        v_joining_day varchar2(9) := :v_joining_day;
      BEGIN 
        EXECUTE IMMEDIATE q'^
          BEGIN
            IF :p_joining_day = 'MONDAY' THEN
              :o_flag := 'Y';
            ELSE
              :o_flag := 'N';
            END IF;
          END;^'
        USING v_joining_day, OUT :o_flag;
      END;
Result flag: Y

The first thing to note in the generated statement is the declare section, which has to list all the possible variable names you might have in input_params, and set them from new bind variables. You must know these already in the main block/procedure, either as local variables or more likely procedure arguments; but they all have the be duplicated here, since at this point you don't know which will be needed.

Then that statement has its own inner dynamic SQL which is essentially what you were originally doing, but concatenates in the input_params string as well as condition.

The important part here is the quoting. In the first one, for example, both :p_end_date and :p_start_date are inside the second level of quotes, within the q'^...^', so they are bound for the inner dynamic SQL, with values from the local v_end_date and v_start_date from that inner execute immediate.

That entire generated block is executed with bind values for all the possible variable names, which provide values for the local variables (via v_start_date date := :v_start_date; etc.) while preserving data types; plus the output flag.

That block then executes its internal execute immediate statement using only the relevant local variables, which now have bound values; and the output flag which is still a bind variable from the outer execute immediate, so the outer block can still see its result.

You can see that the second generated statement uses a different condition and bind variables and values to the first, and the flag is evaluated based on the relevant condition and parameters in each case.


Incidentally, you could remove the duplicate reference to :o_flag (which isn't a problem but I find slightly confusing) by using a case expression instead:

    v_execute_statement := q'[
      DECLARE
        v_start_date date := :v_start_date;
        v_end_date date := :v_end_date;
        v_joining_day varchar2(9) := :v_joining_day;
      BEGIN 
        EXECUTE IMMEDIATE q'^
          BEGIN
            :o_flag := CASE WHEN ]' || rec.condition || q'[ THEN 'Y' ELSE 'N' END;
          END;^'
        USING OUT :v_flag, ]' || rec.input_params || q'[;
      END;]';
Alex Poole
  • 183,384
  • 11
  • 179
  • 318