2013-02-15 87 views
9

Sto cercando di calcolare giorni lavorativi tra due date in Oracle selezionare. Sono arrivato al punto in cui i miei calcoli danno la maggior parte dei risultati corretti per date date (lo paragono con NETWORKDAYS in excel) ma a volte varia da 2 giorni a -2 giorni - e non so perché ...Calcolo giorni lavorativi in ​​Oracle SQL (senza funzioni o procedura)

Ecco il mio codice:

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 

Grazie!

+0

Aggiungi la soluzione come risposta in modo da poterla "accettare" (è meglio che questo sito abbia domande con risposte accettate) –

risposta

21

La soluzione, infine:

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; 

Grazie per tutte le tue risposte!

+0

Penso che tu stia adottando un buon approccio, cercando di utilizzare semplici funzioni SQL invece di una funzione o una tabella di date. Ma i risultati non sono sempre precisi. Ad esempio, se utilizzi le date 2012-02-15 e 2012-02-18, un venerdì e un lunedì, il risultato è 3. –

+2

Beh, in realtà funziona correttamente per tutte le date :) Hai appena usato date errate :) * * 2012-02-15 ** è mercoledì, e il 2012-02-18 è sabato. Immagino tu abbia voluto calcolare gli stessi giorni ma nel 2013 - dà 2 giorni (sempre corretto!). – yochim

+0

+1 Woops, mi dispiace per quello. –

0

Ho modificato l'esempio per renderlo più leggibile e per restituire il conteggio del bus. giorni tra. Non so perché hai bisogno del formato 'J'- Julian. Tutto ciò che serve è avviare/installare e terminare/completare le date. Riceverai il numero corretto di giorni tra 2 date usando questo. Sostituire le mie date con la vostra, aggiungere NLS se necessario ...:

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

Grazie - Sono stato in grado di calcolarlo con il mio metodo – yochim

+0

@yochim - condividerlo con noi. – Art

+0

È condiviso come risposta al mio post originale – yochim

1

Prova questa:

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 

Here is a sqlfiddle demo

+0

Grazie, ho avuto modo di lavorare con il mio approccio :) – yochim

-1

Ecco una funzione rapida e flessibile. Puoi contare tutti i giorni della settimana in un intervallo di date.

CREATE OR REPLACE FUNCTION wfportal.cx_count_specific_weekdays(p_week_days VARCHAR2 DEFAULT 'MON,TUE,WED,THU,FRI' 
                   , p_start_date DATE 
                   , p_end_date DATE) 
RETURN NUMBER 
IS 

/*************************************************************************************************************** 
    * 
    * FUNCTION DESCRIPTION: 
    * 
    * This function calculates the total required week days in a date range. 
    * 
    * PARAMETERS: 
    * 
    * p_week_days VARCHAR2 The week days that need to be counted, comma seperated e.g. MON,TUE,WED,THU,FRU,SAT,SUN 
    * p_start_date DATE  The start date 
    * p_end_date DATE  The end date 
    * 
    * CHANGE history 
    * 
    * No. Date   Changed by  Change Description 
    * ---- ----------- ------------- ------------------------------------------------------------------------- 
    * 0 07-May-2013 yourname   Created 
    * 
    ***************************************************************************************************************/ 

    v_date_end_first_date_range DATE; 
    v_date_start_last_date_range DATE; 
    v_total_days_in_the_weeks  NUMBER; 
    v_total_days_first_date_range NUMBER; 
    v_total_days_last_date_range NUMBER; 
    v_output      NUMBER; 

    v_error_text     CX_ERROR_CODES.ERROR_MESSAGE%TYPE; 

    --Count the required days in a specific date ranges by using a list of all the weekdays in that range. 
    CURSOR c_total_days (v_start_date DATE 
         , v_end_date DATE) IS 
    SELECT COUNT(*) total_days 
    FROM (SELECT (v_start_date + level - 1) days 
      FROM dual 
      CONNECT BY LEVEL <= (v_end_date - v_start_date) + 1 
      ) 
    WHERE INSTR(',' || p_week_days || ',', ',' || TO_CHAR(days, 'DY', 'NLS_DATE_LANGUAGE=english') || ',', 1) > 0 
    ; 

    --Calculate the first and last date range by retrieving the first Sunday after the start date and the last Monday before the end date. 
    --Calculate the total amount of weeks in between and multiply that with the total required days. 
    CURSOR c_calculate_new_dates (v_start_date DATE 
           , v_end_date DATE) IS 
    SELECT date_end_first_date_range 
    ,  date_start_last_date_range 
    ,  ( 
       (
       (date_start_last_date_range - (date_end_first_date_range + 1)) 
      )/7 
      ) * total_required_days total_days_in_the_weeks --The total amount of required days 
    FROM (SELECT v_start_date + DECODE(TO_CHAR(v_start_date, 'DY', 'NLS_DATE_LANGUAGE=english') 
             , 'MON', 6 
             , 'TUE', 5 
             , 'WED', 4 
             , 'THU', 3 
             , 'FRI', 2 
             , 'SAT', 1 
             , 'SUN', 0 
             , 0) date_end_first_date_range 
      ,  v_end_date - DECODE(TO_CHAR(v_end_date, 'DY', 'NLS_DATE_LANGUAGE=english') 
             , 'MON', 0 
             , 'TUE', 1 
             , 'WED', 2 
             , 'THU', 3 
             , 'FRI', 4 
             , 'SAT', 5 
             , 'SUN', 6 
             , 0) date_start_last_date_range 
      ,  REGEXP_COUNT(p_week_days, ',') + 1 total_required_days --Count the commas + 1 to get the total required weekdays 
      FROM dual 
    ) 
    ; 

BEGIN 

    --Verify that the start date is before the end date 
    IF p_start_date < p_end_date THEN 

    --Get the new calculated days. 
    OPEN c_calculate_new_dates(p_start_date, p_end_date); 

     FETCH c_calculate_new_dates INTO v_date_end_first_date_range 
             , v_date_start_last_date_range 
             , v_total_days_in_the_weeks; 

    CLOSE c_calculate_new_dates; 

    --Calculate the days in the first date range 
    OPEN c_total_days(p_start_date, v_date_end_first_date_range); 
     FETCH c_total_days INTO v_total_days_first_date_range; 
    CLOSE c_total_days; 

    --Calculate the days in the last date range 
    OPEN c_total_days(v_date_start_last_date_range, p_end_date); 
     FETCH c_total_days INTO v_total_days_last_date_range; 
    CLOSE c_total_days; 

    --Sum the total required days 
    v_output := v_total_days_first_date_range + v_total_days_last_date_range + v_total_days_in_the_weeks; 

    ELSE 

    v_output := 0; 

    END IF; 

    RETURN v_output; 

    EXCEPTION 

    WHEN OTHERS 
    THEN 

    RETURN NULL; 

END cx_count_specific_weekdays; 
/
+0

Il richiedente non ha richiesto funzioni o procedure. Questo è probabilmente il motivo per cui la tua risposta è stata sottovalutata. – brandong

-1

Qui si va ...

  1. In primo luogo verificare quanti giorni avete ottenuto nella tabella di vacanza, esclusi i giorni di fine settimana.
  2. Ottenere giorni lavorativi (da MON a FRI) tra le 2 date e successivamente sottrarre i giorni festivi.

    create or replace 
    FUNCTION calculate_business_days (p_start_date IN DATE, p_end_date IN DATE) 
         RETURN NUMBER IS 
         v_holidays  NUMBER; 
         v_start_date DATE := TRUNC (p_start_date); 
         v_end_date  DATE := TRUNC (p_end_date); 
         BEGIN 
         IF v_end_date >= v_start_date 
         THEN 
           SELECT COUNT (*) 
           INTO v_holidays 
           FROM holidays 
           WHERE day BETWEEN v_start_date AND v_end_date 
           AND day NOT IN (
             SELECT hol.day 
             FROM holidays hol 
             WHERE MOD(TO_CHAR(hol.day, 'J'), 7) + 1 IN (6, 7) 
           ); 
    
         RETURN GREATEST (NEXT_DAY (v_start_date, 'MON') - v_start_date - 2, 0) 
          + ( ( NEXT_DAY (v_end_date, 'MON') 
            - NEXT_DAY (v_start_date, 'MON') 
            ) 
           /7 
           ) 
           * 5 
          - GREATEST (NEXT_DAY (v_end_date, 'MON') - v_end_date - 3, 0) 
          - v_holidays; 
         ELSE 
           RETURN NULL; 
         END IF; 
    END calculate_business_days; 
    

Dopo di che si può verificare il lavoro svolto, come:

select 
      calculate_business_days('21-AUG-2013','28-AUG-2013') as business_days 
    from dual; 
+0

Il richiedente non ha richiesto funzioni o procedure. Questo è probabilmente il motivo per cui la tua risposta è stata sottovalutata. – brandong

-1

C'è un altro modo più semplice, utilizzando il collegamento da e dual ...

with t as (select to_date('30-sep-2013') end_date, trunc(sysdate) start_date from dual)select count(1) from dual, t where to_char(t.start_date + level, 'D') not in (1,7) connect by t.start_date + level <= t.end_date; 

con Connect da te ottieni tutte le date da start_date fino a end_date. Quindi puoi escludere le date che non ti servono e contare solo le necessarie.

4

ho preso in considerazione tutti i diversi approcci di cui sopra e si avvicinò con una semplice query che ci dà il numero di giorni lavorativi in ​​ciascun mese dell'anno tra due date:

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');

ritiene prego libero di modificare la query secondo necessità.

+0

Grazie mille per questa domanda. L'ho modificato per ridurre il numero di giorni lavorativi dalle festività aziendali e ora sono in grado di utilizzare questo calcolo da solo. – DKSan

0

La soluzione accettata è abbastanza vicina, ma in alcuni casi sembra errata (ad esempio, dal 2/1/2015 al 2-28/2015 o dal 5/1/2015 al 31/05/2015). Ecco una versione raffinata ...

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 parte che gestisce i multipli di 7 (intere settimane) è buona. Ma, quando si considera la porzione parziale settimana, dipende sia il giorno della settimana offset e il numero di giorni nella porzione parziale, secondo la seguente matrice ...

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

vedo che marcato finale la soluzione non è corretta sempre. Supponiamo che InstallDate sia il 1 ° del mese (se cade il sabato) e CompleteDate è il 16 del mese (se cade di domenica)

In tal caso, i giorni lavorativi effettivi sono 10 ma il risultato della query contrassegnato fornirà la risposta come 12. Quindi, dobbiamo trattare anche questo tipo di casi, che ho usato

(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 

linea per gestirlo.

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; 
0

Per rimuovere solo la domenica e il sabato è possibile utilizzare questo

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 conta il numero di giorni tra le due date
(floor((Base_DateDiff + 0 + Start_WeekDay)/7)) conta il numero di domeniche
(floor((Base_DateDiff + 1 + Start_WeekDay)/7)) conta il numero di sabato

1 + TRUNC(InstallDate) - TRUNC(InstallDate, 'IW') ottenere 1 per lunedì a 7 per domenica

0

Questa query può essere utilizzato per andare indietro N giorni dalla data indicata (solo giorni di lavoro)

Per esempio, tornare indietro 15 giorni dal 2017/05/17:

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 
     )) 

Alcuni brevi spiegazione: supponiamo di voler tornare indietro di un giorno da una data specifica - Trova il sabato più vicino che sia inferiore o uguale alla data indicata. - Dal sabato più vicino, torna indietro (N - offset) giorni. l'offset è il numero di giorni lavorativi tra il sabato più vicino e la data specificata (esclusa la data specificata).

* Per tornare M giorni da un Sabato (solo giorni lavorativi), utilizzare questa formula DateOfMonthOfTheSaturday - [M + Piano (M/6) * 2]

-1

questo sarebbe ritorno giorni lavorativi:

(CompleteDate-InstallDate)-2*FLOOR((CompleteDate-InstallDate)/7)- 
    DECODE(SIGN(TO_CHAR(CompleteDate,'D')- 
    TO_CHAR(InstallDate,'D')),-1,2,0)+DECODE(TO_CHAR(CompleteDate,'D'),7,1,0)- 
    DECODE(TO_CHAR(InstallDate,'D'),7,1,0) as BusinessDays, 
Problemi correlati