A melhor maneira de encapsular a lógica complexa do cursor Oracle PL/SQL como uma visualização?

StackOverflow https://stackoverflow.com/questions/20081

  •  09-06-2019
  •  | 
  •  

Pergunta

Escrevi código PL/SQL para desnormalizar uma tabela em um formato muito mais fácil de consultar.O código usa uma tabela temporária para fazer parte de seu trabalho, mesclando algumas linhas da tabela original.

A lógica é escrita como função de tabela em pipeline, seguindo o padrão do artigo vinculado.A função de tabela usa um PRAGMA AUTONOMOUS_TRANSACTION declaração para permitir a manipulação temporária da tabela e também aceita um parâmetro de entrada do cursor para restringir a desnormalização a determinados valores de ID.

Criei então uma view para consultar a função da tabela, passando todos os valores de ID possíveis como um cursor (outros usos da função serão mais restritivos).

Minha pergunta:tudo isso é realmente necessário?Perdi completamente uma maneira muito mais simples de realizar a mesma coisa?

Cada vez que toco em PL/SQL tenho a impressão de que estou digitando demais.

Atualizar: Acrescentarei um esboço da tabela com a qual estou lidando para dar a todos uma ideia da desnormalização de que estou falando.A tabela armazena um histórico de empregos de funcionários, cada um com uma linha de ativação e (possivelmente) uma linha de rescisão.É possível que um funcionário tenha vários empregos simultâneos, bem como o mesmo emprego repetidamente em intervalos de datas não contíguos.Por exemplo:

| EMP_ID | JOB_ID | STATUS | EFF_DATE    | other columns...
|      1 |     10 | A      | 10-JAN-2008 |
|      2 |     11 | A      | 13-JAN-2008 |
|      1 |     12 | A      | 20-JAN-2008 |
|      2 |     11 | T      | 01-FEB-2008 |
|      1 |     10 | T      | 02-FEB-2008 |
|      2 |     11 | A      | 20-FEB-2008 |

Consultar isso para descobrir quem está trabalhando e em qual trabalho não é trivial.Então, minha função de desnormalização preenche a tabela temporária apenas com os intervalos de datas de cada trabalho, para qualquer EMP_IDé passado pelo cursor.Passando EMP_IDs 1 e 2 produziriam o seguinte:

| EMP_ID | JOB_ID | START_DATE  | END_DATE    |
|      1 |     10 | 10-JAN-2008 | 02-FEB-2008 |
|      2 |     11 | 13-JAN-2008 | 01-FEB-2008 |
|      1 |     12 | 20-JAN-2008 |             |
|      2 |     11 | 20-FEB-2008 |             |

(END_DATE permite NULLs para empregos que não têm uma data de término predeterminada.)

Como você pode imaginar, esse formulário desnormalizado é muito, muito mais fácil de consultar, mas criá-lo - até onde eu sei - requer uma tabela temporária para armazenar os resultados intermediários (por exemplo, registros de trabalho para os quais a linha de ativação foi encontrado, mas não a rescisão... ainda).Usar a função de tabela em pipeline para preencher a tabela temporária e depois retornar suas linhas é a única maneira que descobri como fazer isso.

Foi útil?

Solução

Acho que uma maneira de abordar isso é usar funções analíticas...

Eu configurei seu caso de teste usando:

create table employee_job (
    emp_id integer,
    job_id integer,
    status varchar2(1 char),
    eff_date date
    );  

insert into employee_job values (1,10,'A',to_date('10-JAN-2008','DD-MON-YYYY'));
insert into employee_job values (2,11,'A',to_date('13-JAN-2008','DD-MON-YYYY'));
insert into employee_job values (1,12,'A',to_date('20-JAN-2008','DD-MON-YYYY'));
insert into employee_job values (2,11,'T',to_date('01-FEB-2008','DD-MON-YYYY'));
insert into employee_job values (1,10,'T',to_date('02-FEB-2008','DD-MON-YYYY'));
insert into employee_job values (2,11,'A',to_date('20-FEB-2008','DD-MON-YYYY'));

commit;

Eu usei o liderar função para obter a próxima data e, em seguida, agrupar tudo como uma subconsulta apenas para obter os registros "A" e adicionar a data de término, se houver.

select
    emp_id,
    job_id,
    eff_date start_date,
    decode(next_status,'T',next_eff_date,null) end_date
from
    (
    select
        emp_id,
        job_id,
        eff_date,
        status,
        lead(eff_date,1,null) over (partition by emp_id, job_id order by eff_date, status) next_eff_date,
        lead(status,1,null) over (partition by emp_id, job_id order by eff_date, status) next_status
    from
        employee_job
    )
where
    status = 'A'
order by
    start_date,
    emp_id,
    job_id

Tenho certeza de que perdi alguns casos de uso, mas você entendeu.As funções analíticas são suas amigas :)

EMP_ID   JOB_ID     START_DATE     END_DATE            
  1        10       10-JAN-2008    02-FEB-2008         
  2        11       13-JAN-2008    01-FEB-2008         
  2        11       20-FEB-2008                              
  1        12       20-JAN-2008                              

Outras dicas

Em vez de ter o parâmetro de entrada como um cursor, eu teria uma variável de tabela (não sei se o Oracle tem tal coisa, sou um cara do TSQL) ou preencheria outra tabela temporária com os valores de ID e ingressaria nela na visualização /function ou onde você precisar.

O único momento para cursores, na minha opinião honesta, é quando você ter para fazer um loop.E quando você tiver que fazer um loop eu sempre recomendo fazer isso fora do banco de dados na lógica da aplicação.

Parece que você está revelando alguma consistência de leitura aqui, ou seja:será possível que o conteúdo da sua tabela temporária fique fora de sincronia com os dados de origem, se você tiver modificação simultânea de dados.

Sem conhecer os requisitos, nem a complexidade do que se pretende alcançar.eu tentaria

  1. para definir uma visualização, contendo lógica (possivelmente complexa) em SQL; caso contrário, eu adicionaria um pouco de PL/SQL à mistura;
  2. Uma função de tabela em pipeline, mas usando um tipo de coleção SQL (em vez da tabela temporária).Um exemplo simples está aqui: http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:4447489221109

O número 2 lhe daria menos peças móveis e resolveria seu problema de consistência.

Matheus Butler

O verdadeiro problema aqui é o design da tabela "somente gravação" - ou seja, é fácil inserir dados nela, mas é complicado e ineficiente extrair informações úteis dela!Sua tabela “temporária” tem a estrutura que a tabela “permanente” deveria ter em primeiro lugar.

Você poderia fazer isso:

  • Crie uma tabela permanente com a melhor estrutura
  • Preencha-o para corresponder aos dados da primeira tabela
  • Defina um gatilho de banco de dados na tabela original para manter a nova tabela sincronizada de agora em diante

Em seguida, você pode simplesmente selecionar na nova tabela para realizar seus relatórios.

Eu não poderia concordar mais com você, HollyStyles.Eu também costumava ser um cara de TSQL e achava algumas das idiossincrasias da Oracle mais do que um pouco desconcertantes.Infelizmente, as tabelas temporárias não são tão convenientes no Oracle e, nesse caso, outra lógica SQL existente espera consultar diretamente uma tabela, então, em vez disso, forneço a ela essa visualização.Na verdade, não existe lógica de aplicativo fora do banco de dados neste sistema.

Os desenvolvedores Oracle parecem usar cursores com muito mais entusiasmo do que eu imaginava.Dada a natureza de escravidão e disciplina do PL/SQL, isso é ainda mais surpreendente.

A solução mais simples é:

  1. Criar uma tabela temporária global contendo apenas os IDs que você precisa:

    CREATE GLOBAL TEMPORARY TABLE tab_ids (id INTEGER)  
    ON COMMIT DELETE ROWS;
    
  2. Preencha a tabela temporária com os IDs necessários.

  3. Use a operação EXISTS em seu procedimento para selecionar as linhas que estão apenas na tabela de IDs:

      SELECT yt.col1, yt.col2 FROM your\_table yt  
       WHERE EXISTS (  
          SELECT 'X' FROM tab_ids ti  
           WHERE ti.id = yt.id  
       )
    

Você também pode passar uma sequência de IDs separados por vírgula como um parâmetro de função e analisá-la em uma tabela.Isso é realizado por um único SELECT.Quer saber mais - pergunte-me como :-) Mas tem que ser uma pergunta separada.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top