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
  

Mensagemem Qua, 09 Nov 2005 2:40 pm

GAlera, alguém aí, tem uma função pronta q me traga o numero de dias entre duas datas ?
sydfilho
Localização: porto alegre

Mensagemem Qua, 09 Nov 2005 2:50 pm

Sim, é o sinal de MENOS.
data2 - data1 vai retornar o número de dias entre as duas datas.
dr_gori
Localização: Portland, OR USA

Thomas F. G

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

Mensagemem Qua, 09 Nov 2005 2:53 pm

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
sydfilho
Localização: porto alegre

Mensagemem Qua, 09 Nov 2005 3:00 pm

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.

Código: Selecionar todos
create or replace function dias_data( d1 date, d2 date) return number
is
begin
  return  d2 - d1;
end;
/
dr_gori
Localização: Portland, OR USA

Thomas F. G

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

Mensagemem Qui, 02 Fev 2006 8:22 am

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
Toad
Localização: Seattle, WA

Matheus H. Gonçalves
www.toad.com.br
www.twitter.com/toadgeek

Mensagemem Sex, 03 Fev 2006 8:55 am

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.

Código: Selecionar todos
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:
Código: Selecionar todos
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.
dr_gori
Localização: Portland, OR USA

Thomas F. G

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

Mensagemem Seg, 06 Fev 2006 8:34 am

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:

Código: Selecionar todos
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:

Código: Selecionar todos
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
Toad
Localização: Seattle, WA

Matheus H. Gonçalves
www.toad.com.br
www.twitter.com/toadgeek

Mensagemem Seg, 06 Fev 2006 8:52 am

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

Faz assim:
Código: Selecionar todos
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:
Código: Selecionar todos
SQL> select  dias_uteis(sysdate-30, sysdate) uteis from dual;

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

SQL>



:-o
dr_gori
Localização: Portland, OR USA

Thomas F. G

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

Mensagemem Seg, 06 Fev 2006 9:17 am

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

Brigadão!!

Abraços
Toad
Localização: Seattle, WA

Matheus H. Gonçalves
www.toad.com.br
www.twitter.com/toadgeek

Mensagemem Seg, 06 Fev 2006 11:48 am

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

Ficou assim:

Código: Selecionar todos
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
Toad
Localização: Seattle, WA

Matheus H. Gonçalves
www.toad.com.br
www.twitter.com/toadgeek

Mensagemem Seg, 06 Fev 2006 11:51 am

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 !
dr_gori
Localização: Portland, OR USA

Thomas F. G

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

Mensagemem Seg, 06 Fev 2006 12:12 pm

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.
Toad
Localização: Seattle, WA

Matheus H. Gonçalves
www.toad.com.br
www.twitter.com/toadgeek

Mensagemem Seg, 06 Fev 2006 3:05 pm

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

Código: Selecionar todos
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:

Código: Selecionar todos
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!
Toad
Localização: Seattle, WA

Matheus H. Gonçalves
www.toad.com.br
www.twitter.com/toadgeek

Mensagemem Ter, 07 Fev 2006 3:29 pm

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)
leobbg
Localização: PORTO ALEGRE - RS

Leo BBG Consultor Oracle

Mensagemem Qui, 09 Fev 2006 11:54 am

É...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!
Toad
Localização: Seattle, WA

Matheus H. Gonçalves
www.toad.com.br
www.twitter.com/toadgeek

Mensagemem Qui, 17 Dez 2015 7:09 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:
Código: Selecionar todos
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:
Código: Selecionar todos
RETURN (total_dias_uteis-total_feriados);


Executando um exemplo:
Código: Selecionar todos
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:
Código: Selecionar todos
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.

Código: Selecionar todos
SELECT DIAS_UTEIS(2,TO_DATE('2015-02-01'),TO_DATE('2015-02-28'),1) FROM DUAL;

Resultado não conta domingo:
Código: Selecionar todos
TOTAL DIAS:28 - TOTAL FERIADOS:2 - DIAS UTEIS:24 - DIAS UTEIS TOTAL:22

Código: Selecionar todos
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:
Código: Selecionar todos
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:
Código: Selecionar todos
  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';
fabianomaca


  • Veja também
    Respostas
    ExibiÇões
    Última mensagem