Registros da tabela possuem dtinicio > dtfim do reg anter

Dúvidas, dicas e truques de PL/SQL. Aqui também vão assuntos relacionados a pacotes, triggers, funções, Java-Stored Procedures, etc
Responder
Tinho
Rank: DBA Sênior
Rank: DBA Sênior
Mensagens: 321
Registrado em: Seg, 16 Nov 2009 4:50 pm
Localização: São Paulo - SP

Caros colegas,

Estou com um problema de vigência em uma tabela de dados históricos onde, uma chave pode ter vários subitens porém apenas um deles pode estar vigentes, acontece que dentre estes casos existem alguns registros que a vigência do próximo registro começa antes de terminar a sua última! É, parece estranho, talvez um pouco confuso (pelo menos a princípio). Mas vou tentar exemplificar:

Selecionar tudo


ID  SUBID  DTINICIO   DTFIM
1     2   10/05/2010  09/05/2010 -- CORRETO
1     3   08/05/2010  31/12/2010

* Pela regra o que determina se um registro está aberto é a maior data, ou seja, 
31/12/2100, caso a vigência esteja fechada a DTFIM é igual a DTINICIO - 1.

A tabela possui uma quantidade relativamente grande de registros e eu preciso identificar, em quais deles eu possuo caso semelhante.

Pensei em alguns casos, mas talvez pareça tão óbvio que não tenho certeza se me retornará o que eu quero e preciso. A principal dúvida é, como vou retornar o próximo SUBIDs vigente onde a data de início seja menor que a data fim do registro anterior, por isso, fiz algo assim:

Selecionar tudo


SELECT H.*
FROM
( SELECT H1.ID_PC_SERVICO_EQUIP AS ID_PC_SERVICO_EQUIP,
	                 H1.DT_INICIO AS DT_INICIO,
                     H1.DT_FIM AS DT_FIM
  FROM P_DWDB.PC_SERVICO_CARACTERISTICA_HIST H1 
) A, P_DWDB.PC_SERVICO_CARACTERISTICA_HIST H
WHERE A. ID_PC_SERVICO_EQUIP = H. ID_PC_SERVICO_EQUIP
       AND H.DT_INICIO > A.DT_FIM

OU

SELECT DISTINCT
       H.ID_PC_SERVICO_EQUIP AS ID_PC_SERVICO_EQUIP,
       H.ID_CARACTERISTICA AS ID_CARACTERISTICA,
       H.DT_INICIO AS DT_INICIO,
       H.DT_FIM AS DT_FIM,
       A.DT_FIM AS DT_FIM_REGISTRO_ANTERIOR
FROM
( SELECT H1.ID_PC_SERVICO_EQUIP AS ID_PC_SERVICO_EQUIP,
	                 H1.DT_INICIO                           AS DT_INICIO,
                     H1.DT_FIM                                AS DT_FIM
  FROM P_DWDB.PC_SERVICO_CARACTERISTICA_HIST H1 
  WHERE TRIM(SUBSTR( CAST(DT_FIM AS CHAR(10)),1,10)) NOT IN ('2100-12-31')
) A, 
( SELECT COUNT(*)                                    AS QTD,
                     H2.ID_PC_SERVICO_EQUIP  AS ID_PC_SERVICO_EQUIP,
	                 H2.ID_CARACTERISTICA         AS ID_CARACTERISTICA
  FROM P_DWDB.PC_SERVICO_CARACTERISTICA_HIST H2
  GROUP BY 2,3
  HAVING QTD > 1
) B, P_DWDB.PC_SERVICO_CARACTERISTICA_HIST H
WHERE A. ID_PC_SERVICO_EQUIP = H. ID_PC_SERVICO_EQUIP
       AND A.ID_PC_SERVICO_EQUIP  = B.ID_PC_SERVICO_EQUIP 
       AND B. ID_PC_SERVICO_EQUIP = H. ID_PC_SERVICO_EQUIP
       AND TRIM(SUBSTR( CAST(H.DT_FIM AS CHAR(10)),1,10)) IN ('2100-12-31')
       AND H.DT_INICIO < A.DT_FIM       
ORDER BY H.DT_INICIO

* O segundo SELECT apresentado, foi porque eu só preciso verificar os registros das chaves que possuirem mais de um SUBIDs.


Então caros companheiros, se possível, fico no aguardo de algum comentário.

Grato e atenciosamente.
ederphil
Rank: Programador Sênior
Rank: Programador Sênior
Mensagens: 52
Registrado em: Qua, 15 Out 2008 4:05 pm
Localização: taquara-RS

Cara,

não sei se eu entendi bem, mas se você quer buscar somente os registro que tenham mais do que uma ocorrencia e que a data final seja menor que a data inicial, pode usar a tua primeira query com uma subquery,

Selecionar tudo

    SELECT h1.id_pc_servico_equip AS id_pc_servico_equip
              ,h1.dt_inicio           AS dt_inicio
              ,h1.dt_fim              AS dt_fim
          FROM p_dwdb.pc_servico_caracteristica_hist h1
             , (SELECT h2.id_pc_servico_equip id_pc_servico_equip
                     , COUNT(1)
                  FROM p_dwdb.pc_servico_caracteristica_hist h2
                 GROUP BY h2.id_pc_servico_equip
                HAVING COUNT(1) > 1) h2
         WHERE h1.id_pc_servico_equip = h2.id_pc_servico_equip 
           AND h1.dt_inicio > h1.dt_fim
qualquer coisa posta ai!
Tinho
Rank: DBA Sênior
Rank: DBA Sênior
Mensagens: 321
Registrado em: Seg, 16 Nov 2009 4:50 pm
Localização: São Paulo - SP

Opa, valeu.

Então. É mais ou menos isso, vamos tentar chegar num concenso. Pois na verdade eu não sei se a próxima ocorrencia do registro vai ter a data inicial menor, além do que eu preciso garantir que o registro anterior esteja com a vigência fechada e que a chave possua mais de uma ocorrência (detalhes), para que eu tenha um próximo registro.

Eu vou dar uma olhadinha, e analisar melhor o caso e buscar uma solução, com base nas dicas, comentários e sugestões dos colegas.

Novamente obrigado.

Qualquer coisa eu posta aqui.

At.,
ederphil
Rank: Programador Sênior
Rank: Programador Sênior
Mensagens: 52
Registrado em: Qua, 15 Out 2008 4:05 pm
Localização: taquara-RS

aah sim, realmente não era o que eu tinha entendido,

montei um lógica aqui, espero que te ajude,

Selecionar tudo

WITH Q1 AS( --Query, retorna o registro de maior data que esteja fechado
         SELECT h1.id_pc_servico_equip      AS id_pc_servico_equip
--              , h1.dt_inicio                AS dt_inicio
              , MAX(h1.dt_fim)              AS dt_fim
          FROM p_dwdb.pc_servico_caracteristica_hist h1
             , (
                SELECT h2.id_pc_servico_equip id_pc_servico_equip
                     , COUNT(1)
                  FROM p_dwdb.pc_servico_caracteristica_hist h2
                 GROUP BY h2.id_pc_servico_equip
                HAVING COUNT(1) > 1
                ) h2
         WHERE h1.id_pc_servico_equip = h2.id_pc_servico_equip 
           AND h1.dt_inicio > h1.dt_fim),
     Q2 AS(--Query, retorna o registro de maior data que não esteja fechado
        SELECT h1.dt_inicio
             , h1.id_pc_servico_equip
          FROM p_dwdb.pc_servico_caracteristica_hist h1
         WHERE h1.id_pc_servico_equip IN (
                                          SELECT MAX(h1.dt_fim)    
                                               , h1.id_pc_servico_equip
                                            FROM p_dwdb.pc_servico_caracteristica_hist h1
                                           GROUP BY h1.id_pc_servico_equip
                                          )
           AND h1.dt_inicio < h1.dt_fim 
             )
SELECT /*+ MATERIALIZE */ q1.id_pc_servico_equip
--     , q1.dt_fim
--    , q2.dt_inicio
  FROM q1
     , q2
 WHERE q1.id_pc_servico_equip = q2.id_pc_servico_equip
   AND q1.dt_fim > q2.dt_inicio
vlw!!
Tinho
Rank: DBA Sênior
Rank: DBA Sênior
Mensagens: 321
Registrado em: Seg, 16 Nov 2009 4:50 pm
Localização: São Paulo - SP

Hum, interessante, vou dar uma analisada com calma.

Só tem um detalhe, não estou utilizando o Oracle, preciso utilizar recursos disponíveis apenas no ANSI-SQL, estou utilizando o banco de dados Teradata. Além do que nunca entendi muito bem o que faz o "WITH" no Oracle, também achei pouca documentação na internet.

Será que poderia utilizar o "Partition By" ou outra forma de agrupamento, ou não tem nada a ver.

Grato,
ederphil
Rank: Programador Sênior
Rank: Programador Sênior
Mensagens: 52
Registrado em: Qua, 15 Out 2008 4:05 pm
Localização: taquara-RS

Claro,
na realidade com with você declara varias querys, e depois trabalha com elas em um único select,

Selecionar tudo

SELECT q1.id_pc_servico_equip
--     , q1.dt_fim
--     , q2.dt_inicio
  FROM ( --Query, retorna o registro de maior data que esteja fechado
         SELECT h1.id_pc_servico_equip      AS id_pc_servico_equip
--              , h1.dt_inicio                AS dt_inicio
              , MAX(h1.dt_fim)              AS dt_fim
          FROM p_dwdb.pc_servico_caracteristica_hist h1
             , (
                SELECT h2.id_pc_servico_equip id_pc_servico_equip
                     , COUNT(1)
                  FROM p_dwdb.pc_servico_caracteristica_hist h2
                 GROUP BY h2.id_pc_servico_equip
                HAVING COUNT(1) > 1
                ) q1
     , (--Query, retorna o registro de maior data que não esteja fechado
        SELECT h1.dt_inicio
             , h1.id_pc_servico_equip
          FROM p_dwdb.pc_servico_caracteristica_hist h1
         WHERE h1.id_pc_servico_equip IN (
                                          SELECT MAX(h1.dt_fim)    
                                               , h1.id_pc_servico_equip
                                            FROM p_dwdb.pc_servico_caracteristica_hist h1
                                           GROUP BY h1.id_pc_servico_equip
                                          )
           AND h1.dt_inicio < h1.dt_fim 
             ) q2
 WHERE q1.id_pc_servico_equip = q2.id_pc_servico_equip
   AND q1.dt_fim > q2.dt_inicio
qualquer coisa posta ai!
Avatar do usuário
dr_gori
Moderador
Moderador
Mensagens: 5024
Registrado em: Seg, 03 Mai 2004 3:08 pm
Localização: Portland, OR USA
Contato:
Thomas F. G

Você já respondeu a dúvida de alguém hoje?
https://glufke.net/oracle/search.php?search_id=unanswered

É possível fazer isso usando funções analíticas, veja:

Vou criar uma tabela de teste e inserir dados:

Selecionar tudo

create table thomas (id number, subid number, dtinicio date, dtfim date);
/
begin
  delete from thomas;
  insert into thomas values (1,1, '01-may-2010', '09-may-2010');
  insert into thomas values (1,2, '10-may-2010', '12-may-2010');
  insert into thomas values (1,3, '11-may-2010', '31-dec-2100');   --> PROBLEMA
  
  insert into thomas values (2,1, '02-may-2010', '04-may-2010');
  insert into thomas values (2,2, '03-may-2010', '06-may-2010');  --> PROBLEMA
  insert into thomas values (2,3, '08-may-2010', '31-dec-2100');
end;
/
Agora, eu faço um select que mostra a última data ANTERIOR usando funções analíticas: (coluna ANT)

Selecionar tudo

select
  id
, subid
, LAG(dtfim,1,NULL)
  OVER (PARTITION BY id
        ORDER BY id, subid) ant  
, dtinicio
, dtfim
from thomas
/


        ID      SUBID ANT         DTINICIO    DTFIM
---------- ---------- ----------- ----------- -----------
         1          1             1/5/2010    9/5/2010
         1          2 9/5/2010    10/5/2010   12/5/2010
         1          3 12/5/2010   11/5/2010   31/12/2100
         2          1             2/5/2010    4/5/2010
         2          2 4/5/2010    3/5/2010    6/5/2010
         2          3 6/5/2010    8/5/2010    31/12/2100
 
6 rows selected
Agora que sabemos qual é o anterior, basta ver se é maior, veja:

Selecionar tudo

select 
  a.*
, case when ult > dtinicio then 'ERRO'
  end TEM_ERRO
from (
  select
    id
  , subid
  , LAG(dtfim,1,NULL)
    OVER (PARTITION BY id
          ORDER BY id, subid) ant 
  , dtinicio
  , dtfim
  from thomas
) a 
/

       ID      SUBID ANT         DTINICIO    DTFIM       TEM_ERRO
---------- ---------- ----------- ----------- ----------- --------
         1          1             1/5/2010    9/5/2010    
         1          2 9/5/2010    10/5/2010   12/5/2010   
         1          3 12/5/2010   11/5/2010   31/12/2100  ERRO
         2          1             2/5/2010    4/5/2010    
         2          2 4/5/2010    3/5/2010    6/5/2010    ERRO
         2          3 6/5/2010    8/5/2010    31/12/2100  
 
6 rows selected

VOALÁ !!! :-o :-o :-o
Tinho
Rank: DBA Sênior
Rank: DBA Sênior
Mensagens: 321
Registrado em: Seg, 16 Nov 2009 4:50 pm
Localização: São Paulo - SP

Ao colega ederphil,

Hããããã... rs - Então para eu utilizar o WITH eu não preciso declará-lo dentro de um bloco anônimo? Eu conseguiriá o mesmo feito se eu utiliza-se um DECLARE/BEGIN/END?!

--

Dr_Gori, a sua solução é bem prática e objetiva, mas neste caso eu estarei pegando mais de um caso de subids por chave? Acho que sim, por causa do Over Partition By. Porém, como disse estou utilizando o Teradata que por sua vez utiliza ANSI-SQL, além de seu SGBD ser um tanto quanto "pobre" em termos de recursos e biblioteca de funções se comparado ao Oracle.

De qualquer maneira, vou procurar entender melhor o que faz essa função analítica "LAG" e tentar fazer isso de forma "manual".

Que pelo o que eu entendi:

Selecionar tudo


...ela realiza uma busca dentro da mesma tabela e retornará uma linha que esteja anterior à posição atual do cursor.

(fonte: DevMedia - http://www.devmedia.com.br/post-4151-Funcoes-Analiticas-em-Oracle-DENSE_RANK-e-LAG-LEAD.html) 

e

...permite o acesso a duas linhas da mesma tabela sem recorrer a uma junção da tabela com ela própria. Permite comparar uma linha com a seguinte ou com duas à frente. 

(fonte: http://aserlorenzo.com/manSQL/Oracle/dml/funcoes/funcoesanaliticas.htm)

Lendo essa segunda opção, parece-me que foi o que tentei fazer, porém em dois passos. Quero dizer, olhando para a mesma tabela eu peguei a ocorrência de mais de um registro por chave:

Selecionar tudo


( SELECT COUNT(*)                                    AS QTD,
                     H2.ID_PC_SERVICO_EQUIP  AS ID_PC_SERVICO_EQUIP,
	                 H2.ID_CARACTERISTICA         AS ID_CARACTERISTICA
  FROM P_DWDB.PC_SERVICO_CARACTERISTICA_HIST H2
  GROUP BY 2,3
  HAVING QTD > 1
) B

E depois eu peguei um conjunto de registros que estivessem com a vigência fechada:

Selecionar tudo


( SELECT H1.ID_PC_SERVICO_EQUIP AS ID_PC_SERVICO_EQUIP,
	                 H1.DT_INICIO                           AS DT_INICIO,
                     H1.DT_FIM                                AS DT_FIM
  FROM P_DWDB.PC_SERVICO_CARACTERISTICA_HIST H1 
  WHERE TRIM(SUBSTR( CAST(DT_FIM AS CHAR(10)),1,10)) NOT IN ('2100-12-31')
) A

Com estas duas tabelas A e B eu fiz um JOIN simples trazendo todos os registros de A que possuissem alguma ocorrência em B e tivesse mais de um registro por chave. Subindo um "nível" da query, do lado de fora eu peguei a tabela principal, relaciona-a com B e comparei se a data final de A era menor do que a data inicial da tabela principal.

Infelizmente, estou sem o Oracle aqui para proceder com os testes, mas, será que se eu aplicasse a minha solução sobre o seu exemplo daria certo?!

Grato a atenção de todos.
Responder
  • Informação
  • Quem está online

    Usuários navegando neste fórum: Nenhum usuário registrado e 4 visitantes