Oracle
 sql >> Base de données >  >> RDS >> Oracle

Passer des paramètres d'entrée dynamiques pour "exécuter immédiatement"

Vous ne pouvez pas fournir une liste de chaînes de valeurs de liaison en tant que using paramètre, donc la seule façon que je vois pour le faire est avec des appels SQL dynamiques imbriqués, ce qui est un peu désordonné, et signifie devoir déclarer (et lier) tous les paramètres possibles dans le fichier interne. instruction imbriquée et dynamique.

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

J'ai utilisé le mécanisme de cotation alternatif ici pour réduire la confusion des guillemets simples échappés. Il y a deux niveaux imbriqués de guillemets - le niveau extérieur délimité par q'[...]' et celle intérieure délimitée par q'^...^' , mais vous pouvez utiliser d'autres caractères si ceux-ci posent problème en raison du contenu réel de votre table. Échapper à ces citations pour deux niveaux serait assez moche et difficile à suivre/faire correctement ; et vous devrez également vous soucier des guillemets d'échappement supplémentaires dans votre condition chaînes, ce qui poserait déjà un problème avec votre code existant pour le deuxième exemple que vous avez fourni, car il contient un littéral de texte.

Avec vos deux exemples de lignes de tableau et les valeurs de date/jour factices que j'ai montrées au-dessus de la sortie de l'exécution, c'est-à-dire :

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

La première chose à noter dans l'instruction générée est la section declare, qui doit lister tous les noms de variables possibles que vous pourriez avoir dans input_params , et définissez-les à partir de nouvelles variables de liaison. Vous devez déjà les connaître dans le bloc/procédure principal, soit en tant que variables locales, soit en tant qu'arguments de procédure plus probables ; mais ils doivent tous être dupliqués ici, car à ce stade, vous ne savez pas ce qui sera nécessaire.

Ensuite, cette instruction a son propre SQL dynamique interne qui est essentiellement ce que vous faisiez à l'origine, mais concatène dans le input_params chaîne ainsi que condition .

La partie importante ici est la citation. Dans le premier, par exemple, à la fois :p_end_date et :p_start_date sont à l'intérieur du deuxième niveau de guillemets, dans le q'^...^' , ils sont donc liés au SQL dynamique interne, avec des valeurs du v_end_date local et v_start_date à partir de cet intérieur execute immediate .

Ce bloc généré entier est exécuté avec des valeurs de liaison pour tous les noms de variables possibles, qui fournissent des valeurs pour les variables locales (via v_start_date date := :v_start_date; etc.) tout en préservant les types de données ; plus le drapeau de sortie.

Ce bloc exécute ensuite son execute immediate interne déclaration utilisant uniquement les variables locales pertinentes, qui ont maintenant des valeurs liées ; et le drapeau de sortie qui est toujours une variable de liaison de l'extérieur execute immediate , afin que le bloc externe puisse toujours voir son résultat.

Vous pouvez voir que la deuxième instruction générée utilise une condition différente et lie des variables et des valeurs à la première, et l'indicateur est évalué en fonction de la condition et des paramètres pertinents dans chaque cas.

Incidemment, vous pouvez supprimer la référence en double à :o_flag (ce qui n'est pas un problème mais je trouve un peu déroutant) en utilisant une expression case à la place :

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