Oracle
 sql >> Base de Dados >  >> RDS >> Oracle

Passando parâmetros de entrada dinâmicos para 'executar imediato'


Você não pode fornecer uma lista de strings de valores de ligação como um using , então a única maneira que vejo para fazer isso é com chamadas SQL dinâmicas aninhadas, o que é um pouco confuso e significa ter que declarar (e vincular) todos os parâmetros possíveis no arquivo interno. instrução aninhada e dinâmica.
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;
/

Eu usei o mecanismo de cotação alternativo aqui para reduzir a confusão de aspas simples escapadas. Existem dois níveis aninhados de citação - o externo delimitado por q'[...]' e o interno delimitado por q'^...^' , mas você pode usar outros caracteres se esses forem um problema devido ao conteúdo real da tabela. Escapar dessas citações para dois níveis seria muito feio e difícil de seguir/acertar; e você também teria que se preocupar com as aspas de escape em sua condition strings, o que já seria um problema com seu código existente para o segundo exemplo que você forneceu, pois contém um literal de texto dentro dele.

Com suas duas linhas da tabela de amostra e os valores fictícios de data/dia que mostrei acima da saída da execução que é:
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

A primeira coisa a notar na declaração gerada é a seção declare, que deve listar todos os nomes de variáveis ​​possíveis que você pode ter em input_params , e defina-os a partir de novas variáveis ​​de ligação. Você deve conhecê-los já no bloco/procedimento principal, seja como variáveis ​​locais ou argumentos de procedimento mais prováveis; mas todos eles devem ser duplicados aqui, pois neste momento você não sabe qual será necessário.

Então essa instrução tem seu próprio SQL dinâmico interno que é essencialmente o que você estava fazendo originalmente, mas concatena no input_params string, bem como condition .

A parte importante aqui é a citação. No primeiro, por exemplo, tanto :p_end_date e :p_start_date estão dentro do segundo nível de aspas, dentro do q'^...^' , portanto, eles são vinculados ao SQL dinâmico interno, com valores do local v_end_date e v_start_date desse execute immediate interno .

Todo esse bloco gerado é executado com valores de ligação para todos os nomes de variáveis ​​possíveis, que fornecem valores para as variáveis ​​locais (via v_start_date date := :v_start_date; etc.) preservando os tipos de dados; mais o sinalizador de saída.

Esse bloco então executa seu execute immediate interno declaração usando apenas as variáveis ​​locais relevantes, que agora têm valores vinculados; e o sinalizador de saída que ainda é uma variável de ligação do execute immediate externo , para que o bloco externo ainda possa ver seu resultado.

Você pode ver que a segunda instrução gerada usa uma condição diferente e vincula variáveis ​​e valores à primeira, e o sinalizador é avaliado com base na condição e nos parâmetros relevantes em cada caso.

Aliás, você pode remover a referência duplicada para :o_flag (o que não é um problema, mas acho um pouco confuso) usando uma expressão case:
    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;]';