Oracle
 sql >> Datenbank >  >> RDS >> Oracle

Dynamische Eingabeparameter an 'execute Immediate' übergeben

Sie können keine Zeichenfolgenliste mit Bindungswerten als using bereitstellen Parameter, also kann ich dies nur mit verschachtelten dynamischen SQL-Aufrufen tun, was ein bisschen chaotisch ist und bedeutet, dass alle möglichen Parameter im Inneren deklariert (und gebunden) werden müssen. verschachtelte, dynamische Anweisung.

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;
/

Ich habe der alternative Zitiermechanismus hier, um Verwirrung durch einzelne Escape-Anführungszeichen zu vermeiden. Es gibt zwei verschachtelte Zitierebenen - die äußere wird durch q'[...]' begrenzt und die innere durch q'^...^' begrenzt , aber Sie können andere Zeichen verwenden, wenn diese aufgrund Ihrer tatsächlichen Tabelleninhalte ein Problem darstellen. Diese Anführungszeichen für zwei Ebenen zu umgehen, wäre ziemlich hässlich und schwer zu befolgen/richtig zu machen; und Sie müssten sich auch um weitere Escape-Anführungszeichen in Ihrer condition kümmern Zeichenfolgen, was bereits ein Problem mit Ihrem vorhandenen Code für das zweite von Ihnen bereitgestellte Beispiel wäre, da dieser ein Textliteral enthält.

Mit Ihren zwei Beispieltabellenzeilen und den Dummy-Datums-/Tagwerten, die ich oben gezeigt habe, lautet die Ausgabe von running:

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

Das erste, was in der generierten Anweisung zu beachten ist, ist der Deklarationsabschnitt, der alle möglichen Variablennamen auflisten muss, die Sie möglicherweise in input_params haben , und setzen Sie sie aus neuen Bindungsvariablen. Sie müssen diese bereits im Hauptblock/der Hauptprozedur kennen, entweder als lokale Variablen oder eher als Prozedurargumente; aber sie alle müssen hier dupliziert werden, da Sie zu diesem Zeitpunkt nicht wissen, welche benötigt werden.

Dann hat diese Anweisung ihr eigenes inneres dynamisches SQL, was im Wesentlichen das ist, was Sie ursprünglich getan haben, aber in den input_params verkettet wird string sowie condition .

Der wichtige Teil hier ist das Zitieren. Im ersten beispielsweise beide :p_end_date und :p_start_date befinden sich innerhalb der zweiten Ebene der Anführungszeichen innerhalb des q'^...^' , also sind sie für das innere dynamische SQL gebunden, mit Werten aus dem lokalen v_end_date und v_start_date von diesem inneren execute immediate .

Dieser gesamte generierte Block wird mit Bindungswerten für alle möglichen Variablennamen ausgeführt, die Werte für die lokalen Variablen bereitstellen (über v_start_date date := :v_start_date; usw.) unter Beibehaltung von Datentypen; plus das Ausgabe-Flag.

Dieser Block führt dann sein internes execute immediate aus Anweisung, die nur die relevanten lokalen Variablen verwendet, die jetzt gebundene Werte haben; und das Ausgabe-Flag, das immer noch eine Bind-Variable aus dem äußeren execute immediate ist , sodass der äußere Block sein Ergebnis immer noch sehen kann.

Sie können sehen, dass die zweite generierte Anweisung eine andere Bedingung verwendet und Variablen und Werte an die erste bindet, und das Flag in jedem Fall basierend auf der relevanten Bedingung und Parametern ausgewertet wird.

Übrigens könnten Sie den doppelten Verweis auf :o_flag entfernen (was kein Problem ist, aber ich finde es etwas verwirrend), indem Sie stattdessen einen Fallausdruck verwenden:

    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;]';