web-dev-qa-db-fra.com

Calculer les jours ouvrés dans Oracle SQL (pas de fonction ni de procédure)

J'essaie de calculer les jours ouvrables entre deux dates dans Oracle Select. Je suis arrivé au moment où mon calcul donne la plupart des résultats corrects pour des dates données (je le compare avec NETWORKDAYS dans Excel), mais il varie parfois de 2 à -2 jours - et je ne sais pas pourquoi ... 

Voici mon code:

SELECT
((to_char(CompleteDate,'J') - to_char(InstallDate,'J'))+1) - (((to_char(CompleteDate,'WW')+ (52 * ((to_char(CompleteDate,'YYYY') - to_char(InstallDate,'YYYY'))))) - to_char(InstallDate,'WW'))*2) as BusinessDays
FROM TABLE

Merci!

9
yochim

La solution, enfin:

SELECT OrderNumber, InstallDate, CompleteDate,
  (TRUNC(CompleteDate) - TRUNC(InstallDate) ) +1 - 
  ((((TRUNC(CompleteDate,'D'))-(TRUNC(InstallDate,'D')))/7)*2) -
  (CASE WHEN TO_CHAR(InstallDate,'DY','nls_date_language=english')='Sun' THEN 1 ELSE 0 END) -
  (CASE WHEN TO_CHAR(CompleteDate,'DY','nls_date_language=english')='SAT' THEN 1 ELSE 0 END) as BusinessDays
FROM Orders
ORDER BY OrderNumber;

Merci pour toutes vos réponses !

22
yochim

J'ai pris en compte toutes les différentes approches discutées ci-dessus et suis venu avec une requête simple qui nous donne le nombre de jours ouvrables de chaque mois de l'année entre deux dates:

WITH test_data AS ( SELECT TO_DATE('01-JAN-14') AS start_date, TO_DATE('31-DEC-14') AS end_date
FROM dual ), all_dates AS (
SELECT td.start_date, td.end_date, td.start_date + LEVEL-1 as week_day FROM test_data td CONNECT BY td.start_date + LEVEL-1 <= td.end_date) SELECT TO_CHAR(week_day, 'MON'), COUNT(*)
FROM all_dates WHERE to_char(week_day, 'dy', 'nls_date_language=AMERICAN') NOT IN ('Sun' , 'sat') GROUP BY TO_CHAR(week_day, 'MON');

N'hésitez pas à modifier la requête si nécessaire.

5
OraGeek

Essaye ça:

with holidays as 
(
select d from (
select minDate + level -1 d
 from (select min(submitDate) minDate, max (completeDate) maxDate
 from t)
 connect by level <= maxDate - mindate + 1) 
 where to_char(d, 'dy', 'nls_date_language=AMERICAN') not in ('Sun' , 'sat')
)
select t.OrderNo, t.submitDate, t.completeDate, count(*) businessDays
from t join holidays h on h.d between t.submitDate and t.completeDate
group by t.OrderNo, t.submitDate, t.completeDate
order by orderno

Voici une démo de sqlfiddle

1
A.B.Cade

Je vois que la solution finale marquée n'est pas toujours correcte. Supposons que InstallDate est le 1er du mois (si tombe le samedi) et CompleteDate est le 16 du mois (si tombe le dimanche)

Dans ce cas, le nombre réel de jours ouvrables est de 10, mais le résultat de la requête marqué donnera la réponse de 12. Donc, nous devons également traiter ce type de cas, ce que j'ai utilisé

(CASE WHEN TO_CHAR(InstallDate,'DY','nls_date_language=english')='SAT' AND TO_CHAR(CompleteDate,'DY','nls_date_language=english')='Sun' THEN 2 ELSE 0 END

ligne pour le gérer. 

SELECT OrderNumber, InstallDate, CompleteDate,
(TRUNC(CompleteDate) - TRUNC(InstallDate) ) +1 - 
((((TRUNC(CompleteDate,'D'))-(TRUNC(InstallDate,'D')))/7)*2) -
(CASE WHEN TO_CHAR(InstallDate,'DY','nls_date_language=english')='Sun' THEN 1 ELSE 0 END) -
(CASE WHEN TO_CHAR(CompleteDate,'DY','nls_date_language=english')='SAT' THEN 1 ELSE 0 END) -
(CASE WHEN TO_CHAR(InstallDate,'DY','nls_date_language=english')='SAT' AND TO_CHAR(CompleteDate,'DY','nls_date_language=english')='Sun' THEN 2 ELSE 0 END)as BusinessDays
FROM Orders
ORDER BY OrderNumber;
1
Hiran

La solution acceptée est assez proche mais semble dans certains cas être fausse (par exemple, du 2/01/2015 au 2-28/2015 ou du 5/1/2015 au 5/31/2015). Voici une version raffinée ...

  end_date-begin_date+1 /* total days */
  - TRUNC(2*(end_date-begin_date+1)/7) /* weekend days in whole weeks */
  - (CASE
      WHEN TO_CHAR(begin_date,'D') = 1 AND REMAINDER(end_date-begin_date+1,7) > 0 THEN 1
      WHEN TO_CHAR(begin_date,'D') = 8 - REMAINDER(end_date-begin_date+1,7) THEN 1
      WHEN TO_CHAR(begin_date,'D') > 8 - REMAINDER(end_date-begin_date+1,7) THEN 2
      ELSE 0
    END) /* weekend days in partial week */
  AS business_days

La partie qui gère les multiples de 7 (semaines entières) est bonne. Mais, en considérant la portion de semaine partielle, cela dépend à la fois du décalage du jour de la semaine et du nombre de jours dans la portion partielle, selon la matrice suivante ...

   654321
1N 111111
2M 100000
3T 210000
4W 221000
5R 222100
6F 222210
7S 222221
0
deanashuff

Cette requête peut être utilisée pour revenir en arrière de N jours à compter de la date donnée (jours ouvrables uniquement).

Par exemple, reculez de 15 jours à partir du 17/05/2017:

select date_point, closest_saturday - (15 - offset + floor((15 - offset) / 6) * 2) from(
   select date_point,
          closest_saturday,
          (case
             when weekday_num > 1 then
              weekday_num - 2
             else
              0
           end) offset
    from (
           select  to_date('2017-05-17', 'yyyy-mm-dd') date_point,
                   to_date('2017-05-17', 'yyyy-mm-dd') - to_char(to_date('2017-05-17', 'yyyy-mm-dd'), 'D') closest_saturday,
                   to_char(to_date('2017-05-17', 'yyyy-mm-dd'), 'D') weekday_num
           from dual
          ))

Une brève explication: supposons que nous voulions revenir en arrière de N jours à partir d’une date donnée - Trouver le samedi le plus proche inférieur ou égal à la date donnée. offset) jours. offset est le nombre de jours ouvrables entre le samedi le plus proche et la date donnée (à l'exclusion de la date donnée).

* Pour revenir en arrière M jours d'un samedi (jours ouvrables uniquement), utilisez cette formule DateOfMonthOfTheSaturday - [M + Floor (M/6) * 2]

0
beckham12a18

J'ai changé mon exemple en plus lisible et pour rendre le nombre de bus. jours entre. Je ne sais pas pourquoi vous avez besoin du format 'J'-Julian. Il suffit de commencer/installer et de terminer/dates complètes. Vous obtiendrez ainsi le nombre de jours correct entre 2 dates. Remplacez mes dates par les vôtres, ajoutez NLS si nécessaire ...:

 SELECT Count(*) BusDaysBtwn
  FROM
  (
  SELECT TO_DATE('2013-02-18', 'YYYY-MM-DD') + LEVEL-1 InstallDate  -- MON or any other day 
       , TO_DATE('2013-02-25', 'YYYY-MM-DD') CompleteDate           -- MON or any other day
       , TO_CHAR(TO_DATE('2013-02-18', 'YYYY-MM-DD') + LEVEL-1, 'DY') InstallDay   -- day of week
    FROM dual 
  CONNECT BY LEVEL <= (TO_DATE('2013-02-25', 'YYYY-MM-DD') - TO_DATE('2013-02-18', 'YYYY-MM-DD')) -- end_date - start_date 
   )
   WHERE InstallDay NOT IN ('SAT', 'Sun')
  /

  SQL> 5
0
Art

Pour supprimer uniquement les dimanches et samedis, vous pouvez utiliser cette

SELECT Base_DateDiff
     - (floor((Base_DateDiff + 0 + Start_WeekDay) / 7))
     - (floor((Base_DateDiff + 1 + Start_WeekDay) / 7))
FROM   (SELECT 1 + TRUNC(InstallDate) - TRUNC(InstallDate, 'IW') Start_WeekDay
             , CompleteDate - InstallDate + 1 Base_DateDiff
        FROM TABLE) a

Base_DateDiff compte le nombre de jours entre les deux dates
(floor((Base_DateDiff + 0 + Start_WeekDay) / 7)) compte le nombre de dimanches
(floor((Base_DateDiff + 1 + Start_WeekDay) / 7)) compte le nombre de samedis

1 + TRUNC(InstallDate) - TRUNC(InstallDate, 'IW') obtenez-en 1 du lundi au 7 de dimanche

0
Serpiton