Numero de dias entre duas datas

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
sydfilho
Rank: Estagiário Pleno
Rank: Estagiário Pleno
Mensagens: 7
Registrado em: Seg, 14 Mar 2005 3:36 pm
Localização: porto alegre

GAlera, alguém aí, tem uma função pronta q me traga o numero de dias entre duas datas ?
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

Sim, é o sinal de MENOS.
data2 - data1 vai retornar o número de dias entre as duas datas.
sydfilho
Rank: Estagiário Pleno
Rank: Estagiário Pleno
Mensagens: 7
Registrado em: Seg, 14 Mar 2005 3:36 pm
Localização: porto alegre

td bem, só queria saber se alguém já tinha uma funçãozinha q passando a data como parâmetro, retornasse o valor, para me adiantar aqui..de qualquer maneira, valeu
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

Mas eu não entendo.. pra que você quer uma função pra isso?
Se você tiver 2 datas e quiser saber o número de dias entre elas, basta subtratir uma da outra.

você pode fazer uma função pra isso sim, mas é só pra confundir quem vai mexer no sistema depois.

Selecionar tudo

create or replace function dias_data( d1 date, d2 date) return number
is
begin
  return  d2 - d1;
end;
/
Avatar do usuário
Toad
Rank: DBA Pleno
Rank: DBA Pleno
Mensagens: 253
Registrado em: Sex, 18 Nov 2005 2:14 pm
Localização: Seattle, WA
Contato:
Matheus Gonçalves
matheus.dev
twitter.com/developer__c

Não sei se é essa a dúvida dele, mas acabei de me deparar com esse problema.

Tenho uma data DATA1 e uma data DATA2.

Como eu faço para calcular quantos dias úteis existem entre DATA1 e DATA2!? (Não conta fins de semana nem feriados nacionais).

Imagino que vá dar uma função enorme... alguém tem algo relacionado por aí?

Abraços!!
Obrigado!

Toad
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

Acho que o jeito mais fácil é você criar um programa que conta quantos dias tem entre uma data e outra e faz um LOOP. Caso o dia da semana for de segunda a sexta, soma em um contador...

Eu fiz um SQL aqui que faz mais ou menos a mesma coisa. Talvez fazer uma rotina em PL/SQL seja mais rápida que esse sql.

Selecionar tudo

declare
  vtemp  number;
  vdata1 date;
  vdata2 date;
begin
  vdata1 := to_date('03/02/2006','dd/mm/rrrr');
  vdata2 := to_date('28/02/2006','dd/mm/rrrr');

  select count(dat)
  into vtemp
  from
    ( 
    select vdata1 + rownum-1 dat
    from all_tables
    where rownum <= ( vdata2-vdata1)+1
    )
  where to_char(dat, 'd') not in (1,7);
  
  dbms_output.put_line('Numero de dias úteis: '||vtemp);
end;
Vamos ao exemplo:

Selecionar tudo

SQL> set serveroutput on
SQL> declare
  2    vtemp  number;
  3    vdata1 date;
  4    vdata2 date;
  5  begin
  6    vdata1 := to_date('03/02/2006','dd/mm/rrrr');
  7    vdata2 := to_date('28/02/2006','dd/mm/rrrr');
  8  
  9    select count(dat)
 10    into vtemp
 11    from
 12      ( 
 13      select vdata1 + rownum-1 dat
 14      from all_tables
 15      where rownum <= ( vdata2-vdata1)+1
 16      )
 17    where to_char(dat, 'd') not in (1,7);
 18    
 19    dbms_output.put_line('Numero de dias úteis: '||vtemp);
 20  end;
 21  /
Numero de dias úteis: 18

PL/SQL procedure successfully completed.

SQL> 
Editado pela última vez por dr_gori em Seg, 06 Fev 2006 8:39 am, em um total de 1 vez.
Avatar do usuário
Toad
Rank: DBA Pleno
Rank: DBA Pleno
Mensagens: 253
Registrado em: Sex, 18 Nov 2005 2:14 pm
Localização: Seattle, WA
Contato:
Matheus Gonçalves
matheus.dev
twitter.com/developer__c

Fala Dr.
Tudo tranquilo? Obrigado desde já pela ajuda.

Tentei gerar uma função com o sql que você passou, ficou mais ou menos assim:

Selecionar tudo

FUNCTION DIAS_UTEIS(DATA1 IN DATA, DATA2 IN DATA) RETURN NUMBER IS

  vdata1 date := to_date(DATA1,'dd/mm/rrrr');
  vdata2 date := to_date(DATA2,'dd/mm/rrrr');
  vtemp  number;

begin

  select count(dat)into vtemp
  from (select vdata1 + rownum-1 dat from all_tables where rownum <= ( vdata2-vdata1)+1 )
  where to_char(dat, 'd') not in (1,7);
  RETURN vtemp;

end;
Mas está dando erro em "from ( select" ...

Tentei fazer via cursor também. Ficou assim:

Selecionar tudo

FUNCTION DIAS_UTEIS(DATA1 IN DATA, DATA2 IN DATA) RETURN NUMBER IS

 

  vdata1 date := to_date(DATA1,'dd/mm/rrrr');
  vdata2 date := to_date(DATA2,'dd/mm/rrrr');
  vtemp  number;

  cursor DATA is   select count(dat)
                   from (
                          select vdata1 + rownum-1 dat 
                          from all_tables where rownum <= ( vdata2-vdata1)+1 
                         )
                   where to_char(dat, 'd') not in (1,7);
begin
  OPEN  DATA;
  FETCH DATA INTO vtemp;
  CLOSE DATA;
  RETURN vtemp;
end;
Mas também dá erro no parênteses..... O que estou fazendo de errado?:?:

Abraços e obrigado desde já!

Matheus
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

O problema é que você está declarando DATA1 IN DATA...
Deve ser DATE...

Faz assim:

Selecionar tudo

create or replace FUNCTION DIAS_UTEIS( vDATA1 IN DATE, vDATA2 IN DATE) RETURN NUMBER IS
  vtemp  number;
begin
  select count(dat)into vtemp
  from (select vdata1 + rownum-1 dat from all_tables where rownum <= ( vdata2-vdata1)+1 )
  where to_char(dat, 'd') not in (1,7);
  RETURN vtemp;
end; 
Testando:

Selecionar tudo

SQL> select  dias_uteis(sysdate-30, sysdate) uteis from dual;

     UTEIS
----------
        21

SQL> 

:-o
Avatar do usuário
Toad
Rank: DBA Pleno
Rank: DBA Pleno
Mensagens: 253
Registrado em: Sex, 18 Nov 2005 2:14 pm
Localização: Seattle, WA
Contato:
Matheus Gonçalves
matheus.dev
twitter.com/developer__c

Nuossa!! Faz de conta q você não viu isso, beleza!? hahahahaha

Brigadão!!

Abraços
Avatar do usuário
Toad
Rank: DBA Pleno
Rank: DBA Pleno
Mensagens: 253
Registrado em: Sex, 18 Nov 2005 2:14 pm
Localização: Seattle, WA
Contato:
Matheus Gonçalves
matheus.dev
twitter.com/developer__c

Incluí agora a opção para ele não contar os feriados nacionais.

Ficou assim:

Selecionar tudo

create or replace FUNCTION DIAS_UTEIS( vDATA1 IN DATE, vDATA2 IN DATE) RETURN NUMBER IS
  vtemp  number;
  fer_01 varchar2(5) := '01/01';
  fer_02 varchar2(5) := '14/04';
  fer_03 varchar2(5) := '21/04';
  fer_04 varchar2(5) := '01/05';
  fer_05 varchar2(5) := '15/06';
  fer_06 varchar2(5) := '07/09';
  fer_07 varchar2(5) := '12/10';
  fer_08 varchar2(5) := '02/11';
  fer_09 varchar2(5) := '15/11';
  fer_10 varchar2(5) := '25/12';
begin
  select count(dat)into vtemp
  from (select vdata1 + rownum-1 dat from i_os where rownum <= (vdata2-vdata1)+1 )
  where to_char(dat, 'd') not in (1,7)
        and (to_char(dat,'dd/mm') <> fer_01)
        and (to_char(dat,'dd/mm') <> fer_02)
        and (to_char(dat,'dd/mm') <> fer_03)
        and (to_char(dat,'dd/mm') <> fer_04)
        and (to_char(dat,'dd/mm') <> fer_05)
        and (to_char(dat,'dd/mm') <> fer_06)
        and (to_char(dat,'dd/mm') <> fer_07)
        and (to_char(dat,'dd/mm') <> fer_08)
        and (to_char(dat,'dd/mm') <> fer_09)
        and (to_char(dat,'dd/mm') <> fer_10);
  vtemp := vtemp - 1;
  RETURN vtemp;
end; 
Talvez usando and (to_char(dat,'dd/mm') not in (fer_01, fer_02... também funcione, mas não testei!

Obrigado e abraços!

Matheus
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

Acho que o ideal é você criar uma tabela de feriados.
Daí basta você colocar NOT IN (select feriado from TB_FERIADOS)... saca ?

Dessa forma, você está engessando os feriados. Cada vez, terá que alterar a rotina... Também você não pode tratar feriados específicos de cada estado da forma engessada...

Com uma tabela, fica tudo bem flexivel !
Avatar do usuário
Toad
Rank: DBA Pleno
Rank: DBA Pleno
Mensagens: 253
Registrado em: Sex, 18 Nov 2005 2:14 pm
Localização: Seattle, WA
Contato:
Matheus Gonçalves
matheus.dev
twitter.com/developer__c

Claro... você tem razão... na verdade a idéia é só com os feriados nacionais, mas com uma tabela, e não engessado, casa exista alguma alteração e/ou adição de feriado, fica bem mais simples...
Obrigado.
Avatar do usuário
Toad
Rank: DBA Pleno
Rank: DBA Pleno
Mensagens: 253
Registrado em: Sex, 18 Nov 2005 2:14 pm
Localização: Seattle, WA
Contato:
Matheus Gonçalves
matheus.dev
twitter.com/developer__c

Seguindo essa linha de raciocínio, ficou assim a função:

Selecionar tudo

create or replace FUNCTION DIAS_UTEIS( vDATA1 IN DATE, vDATA2 IN DATE) RETURN NUMBER IS
  vtemp  number;
begin
  select count(dat)into vtemp
  from (select vdata1 + rownum-1 dat from i_os where rownum <= (vdata2-vdata1)+1 )
  where to_char(dat, 'd') not in (1,7)
        and to_char(dat,'dd/mm') not in (select dia from i_feriado);
  vtemp := vtemp - 1;
  RETURN vtemp;
end; 

E a tabela assim:

Selecionar tudo

SQL> desc i_feriado
 Name                            Null?    Type
 ------------------------------- -------- ----
 CODIGO                                   NUMBER(5)
 DIA                                      VARCHAR2(5)
 DESCRICAO                                VARCHAR2(60)

SQL> select * from i_feriado
  2  /

   CODIGO DIA   DESCRICAO
--------- ----- ------------------------------------------------------------
        1 01/01 Confraternizacao Universal
        2 14/04 Paixão de Cristo
        3 21/04 Tiradentes
        4 01/05 Dia do Trabalho
        5 15/06 Corpus Christi
        6 09/07 Data Magna de SP
        7 20/08 Aniversário SBC
        8 07/09 Independencia
        9 12/10 Nossa Senhora Aparecida
       10 02/11 Finados
       11 15/11 Proclamação da República
       12 25/12 Natal

12 rows selected.
Agora, se for necessário alterar as datas, é só mexer na tabela e não na função!

Abraços!
Avatar do usuário
leobbg
Rank: Programador Júnior
Rank: Programador Júnior
Mensagens: 22
Registrado em: Sex, 29 Out 2004 10:25 am
Localização: PORTO ALEGRE - RS
Leo BBG Consultor Oracle

Cara, apenas para complementar,...

É melhor incluir o ano na tabela de feriados tmb.. pois existem alguns feriados que mudam de um ano para o outro um exemplo deles é o Carnaval.

valeu!! 8)
Avatar do usuário
Toad
Rank: DBA Pleno
Rank: DBA Pleno
Mensagens: 253
Registrado em: Sex, 18 Nov 2005 2:14 pm
Localização: Seattle, WA
Contato:
Matheus Gonçalves
matheus.dev
twitter.com/developer__c

É...eu não cadastrei aí, mas na minha tabela também tem os feriados dos carnavais desde 1990. Se interessar para alguém, é só pedir!
Abraços!
fabianomaca
Rank: Estagiário Júnior
Rank: Estagiário Júnior
Mensagens: 1
Registrado em: Qui, 17 Dez 2015 6:43 pm

Olá pessoal. Estava procurando algo desse tipo na net pois enfrentava este dilema de calcular dias úteis no Oracle.

Cansado de procurar, resolvi criar uma função pra isso. Ela conta tirando os feriados locais e nacionais cadastrados na tabela e conta caso queira por exemplo considerar sábado e/ou domingo como dia útil. Este ultimo, porque eu trabalho com turmas de treinamentos e algumas delas eram faturadas pelo fornecedor também nos sábados e/ou domingos, então eu tinha que considerar como dia útil.

A tabela de feriados esta separada por estado porque como tem feriados locais também, tive que separar assim.

A função ficou assim:

Selecionar tudo

create or replace FUNCTION DIAS_UTEIS(vCOD_UF INT, vDATAI IN DATE, vDATAF IN DATE, PERIODO IN INT) RETURN VARCHAR2 AS
total_dias NUMBER;
total_feriados NUMBER;
total_dias_uteis NUMBER := 0;
/*
s = dias da semana que devemos considerar
0 conta sabado e domingo
1 não conta domingo
2 não conta sabado e domingo
*/
begin

  /*
  CONTA A QUANTIDADE DE DIAS ENTRE AS DATAS
  SEM VERIFICAR FERIADOS, SÁBADOS E DOMINGOS
  */
  FOR CUR_ROW IN (SELECT TO_DATE(vDATAF)-TO_DATE(vDATAI) TTDIAS FROM DUAL)
    LOOP 
      total_dias:=CUR_ROW.TTDIAS;
    END LOOP;
  
  /*
  CHECA SE EXISTE ALGUM DIA ENTRE AS DATAS NA LISTA DE FERIADOS E SEPARA A CONTAGEM PARA CONSIDERAR:
  0 = CONTA SÁBADO E DOMINDO
  1 = NÃO CONTA FERIADOS DE DOMINGO
  2 = NÃO CONTA FERIADOS DE SÁBADO E DOMINGO
  
  PORQUE ISSO?
  VOCÊ PODE QUERER CONSIDERAR SÁBADO COMO DIA UTIL POR EXEMPLO.
  */
  IF PERIODO = 0  THEN
    FOR CUR_ROW IN (SELECT COUNT(DATA_FERIADO) TT_FERIADOS FROM GIP_TAB_GBL_FERIADOS WHERE DATA_FERIADO BETWEEN TO_DATE(vDATAI) AND TO_DATE(vDATAF) AND COD_UF=vCOD_UF)
      LOOP 
        total_feriados:=CUR_ROW.TT_FERIADOS;
      END LOOP;
  ELSIF PERIODO = 1 THEN 
    FOR CUR_ROW IN (SELECT SUM(CASE WHEN TO_CHAR(DATA_FERIADO,'DY') NOT IN('DOM') THEN 1 ELSE 0 END) TT_FERIADOS FROM GIP_TAB_GBL_FERIADOS WHERE DATA_FERIADO BETWEEN TO_DATE(vDATAI) AND TO_DATE(vDATAF) AND COD_UF=vCOD_UF)
      LOOP 
        total_feriados:=CUR_ROW.TT_FERIADOS;
      END LOOP;
  ELSIF PERIODO = 2 THEN 
    FOR CUR_ROW IN (SELECT SUM(CASE WHEN TO_CHAR(DATA_FERIADO,'DY') NOT IN('SÁB','DOM') THEN 1 ELSE 0 END) TT_FERIADOS FROM GIP_TAB_GBL_FERIADOS WHERE DATA_FERIADO BETWEEN TO_DATE(vDATAI) AND TO_DATE(vDATAF) AND COD_UF=vCOD_UF)
      LOOP 
        total_feriados:=CUR_ROW.TT_FERIADOS;
      END LOOP;
  END IF;
  
  
  /*
  PERCORRE DIA POR DIA SEMPRE CHECANDO SE DESEJA CONTAR OU NÃO SABADO E DOMINGO
  */
  FOR i IN 0 .. total_dias
    LOOP
      IF PERIODO = 0  THEN
        FOR CUR_ROW IN (SELECT TO_DATE(vDATAI)+i FROM DUAL)
          LOOP  
            total_dias_uteis:=total_dias_uteis+1;
          END LOOP;
      ELSIF PERIODO = 1 THEN 
        FOR CUR_ROW IN (SELECT (CASE WHEN TO_CHAR(TO_DATE(vDATAI)+i,'DY') NOT IN('DOM') THEN 1 ELSE 0 END) TT FROM DUAL)
          LOOP  
            total_dias_uteis:=total_dias_uteis++CUR_ROW.TT;
          END LOOP;
      ELSIF PERIODO = 2 THEN 
         FOR CUR_ROW IN (SELECT (CASE WHEN TO_CHAR(TO_DATE(vDATAI)+i,'DY') NOT IN('SÁB','DOM') THEN 1 ELSE 0 END) TT FROM DUAL)
          LOOP  
            total_dias_uteis:=total_dias_uteis+CUR_ROW.TT;
          END LOOP;
      END IF;
    END LOOP;
 
    total_dias:=total_dias+1;
    
    
  RETURN 'TOTAL DIAS:'||total_dias||' - TOTAL FERIADOS:'||total_feriados||' - DIAS UTEIS:'||total_dias_uteis||' - DIAS UTEIS TOTAL:'||(total_dias_uteis-total_feriados);
END;
O return dela esta como varchar apenas para testar a conta e verificar se esta fazendo certo. Para pegar o resultado final, substitua por:

Selecionar tudo

RETURN (total_dias_uteis-total_feriados);
Executando um exemplo:

Selecionar tudo

SELECT DIAS_UTEIS(2,TO_DATE('2015-02-01'),TO_DATE('2015-02-28'),0) FROM DUAL;
A primeira variável da função é o código do estado, no meu caso tenho 2 como GO;
A segunda é a data inicial;
A terceira é a data final;
A quarta é o indicativo para contar ou não sábado e/ou domingo conforme abaixo:
0 conta sábado e domingo
1 não conta domingo
2 não conta sábado e domingo

Este SELECT resulta em:

Selecionar tudo

TOTAL DIAS:28 - TOTAL FERIADOS:2 - DIAS UTEIS:28 - DIAS UTEIS TOTAL:26
*Os dois dias de feriado é por causa do Carnaval cadastrado para os dias 16 e 17/02/2015.

Selecionar tudo

SELECT DIAS_UTEIS(2,TO_DATE('2015-02-01'),TO_DATE('2015-02-28'),1) FROM DUAL;
Resultado não conta domingo:

Selecionar tudo

TOTAL DIAS:28 - TOTAL FERIADOS:2 - DIAS UTEIS:24 - DIAS UTEIS TOTAL:22

Selecionar tudo

SELECT DIAS_UTEIS(2,TO_DATE('2015-02-01'),TO_DATE('2015-02-28'),2) FROM DUAL;
Resultado não conta sábado e domingo:

Selecionar tudo

TOTAL DIAS:28 - TOTAL FERIADOS:2 - DIAS UTEIS:20 - DIAS UTEIS TOTAL:18
Espero que ajude muita gente assim como me ajudou e se alguém quiser melhorar ou sugerir algo, fiquem a vontade.

Dúvidas, só perguntar.

Obrigado aos demais que ajudaram de forma indireta.


Tabela de feriados:

Selecionar tudo

  CREATE TABLE "USER_GIP_HLG"."GIP_TAB_GBL_FERIADOS" 
   (	"COD_UF" NUMBER(*,0), 
	"DIA" NUMBER(*,0), 
	"DATA_FERIADO" DATE, 
	"FERIADO" VARCHAR2(200 BYTE), 
	"NACIONAL" NUMBER(*,0)
   ) SEGMENT CREATION IMMEDIATE 
  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 
 NOCOMPRESS LOGGING
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
  BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
  TABLESPACE "TBL_BASE" ;

   COMMENT ON COLUMN "USER_GIP_HLG"."GIP_TAB_GBL_FERIADOS"."NACIONAL" IS '0 - NACIONAL | 1 - MUNICIPAL';
Responder
  • Informação