Contare i giorni lavorativi tra due date


163

Come posso calcolare il numero di giorni lavorativi tra due date in SQL Server?

Dal lunedì al venerdì e deve essere T-SQL.


5
Puoi definire i giorni feriali? ogni lunedì a venerdì? Escludendo le festività principali? Che paese? Deve essere fatto in SQL?
Dave K,

Risposte:


301

Per i giorni lavorativi, dal lunedì al venerdì, puoi farlo con un singolo SELEZIONA, in questo modo:

DECLARE @StartDate DATETIME
DECLARE @EndDate DATETIME
SET @StartDate = '2008/10/01'
SET @EndDate = '2008/10/31'


SELECT
   (DATEDIFF(dd, @StartDate, @EndDate) + 1)
  -(DATEDIFF(wk, @StartDate, @EndDate) * 2)
  -(CASE WHEN DATENAME(dw, @StartDate) = 'Sunday' THEN 1 ELSE 0 END)
  -(CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)

Se vuoi includere le vacanze, devi risolverlo un po '...


3
Ho appena capito che questo codice non funziona sempre! ho provato questo: SET @StartDate = '28 -mar-2011 'SET @EndDate = '29 -mar-2011' la risposta lo ha contato come 2 giorni
greektreat

16
@greektreat Funziona bene. È solo che sia @StartDate che @EndDate sono inclusi nel conteggio. Se vuoi che da lunedì a martedì valga come 1 giorno, rimuovi semplicemente "+ 1" dopo il primo DATEDIFF. Quindi otterrai anche Ven-> Sab = 0, Ven-> Dom = 0, Ven-> Lun = 1.
Joe Daley,

6
Come seguito a @JoeDaley. Quando rimuovi + 1 dopo DATEDIFF per escludere la data di inizio dal conteggio, devi anche modificare la parte CASE di questo. Ho finito per usare questo: + (CASE WHEN DATENAME (dw, @StartDate) = 'Saturday' THEN 1 ELSE 0 END) - (CASE WHEN DATENAME (dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)
Sequenzia

7
La funzione nome dati dipende dalle impostazioni locali. Una soluzione più solida ma anche più oscura è quella di sostituire le ultime due righe con:-(case datepart(dw, @StartDate)+@@datefirst when 8 then 1 else 0 end) -(case datepart(dw, @EndDate)+@@datefirst when 7 then 1 when 14 then 1 else 0 end)
Torben Klein

2
Per chiarire il commento di @ Sequenzia, vorresti RIMUOVERE completamente le dichiarazioni sul caso di domenica, lasciando solo+(CASE WHEN DATENAME(dw, @StartDate) = 'Saturday' THEN 1 ELSE 0 END) - (CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)
Andy Raddatz,

32

In Calculation Work Days puoi trovare un buon articolo su questo argomento, ma come puoi vedere non è così avanzato.

--Changing current database to the Master database allows function to be shared by everyone.
USE MASTER
GO
--If the function already exists, drop it.
IF EXISTS
(
    SELECT *
    FROM dbo.SYSOBJECTS
    WHERE ID = OBJECT_ID(N'[dbo].[fn_WorkDays]')
    AND XType IN (N'FN', N'IF', N'TF')
)
DROP FUNCTION [dbo].[fn_WorkDays]
GO
 CREATE FUNCTION dbo.fn_WorkDays
--Presets
--Define the input parameters (OK if reversed by mistake).
(
    @StartDate DATETIME,
    @EndDate   DATETIME = NULL --@EndDate replaced by @StartDate when DEFAULTed
)

--Define the output data type.
RETURNS INT

AS
--Calculate the RETURN of the function.
BEGIN
    --Declare local variables
    --Temporarily holds @EndDate during date reversal.
    DECLARE @Swap DATETIME

    --If the Start Date is null, return a NULL and exit.
    IF @StartDate IS NULL
        RETURN NULL

    --If the End Date is null, populate with Start Date value so will have two dates (required by DATEDIFF below).
     IF @EndDate IS NULL
        SELECT @EndDate = @StartDate

    --Strip the time element from both dates (just to be safe) by converting to whole days and back to a date.
    --Usually faster than CONVERT.
    --0 is a date (01/01/1900 00:00:00.000)
     SELECT @StartDate = DATEADD(dd,DATEDIFF(dd,0,@StartDate), 0),
            @EndDate   = DATEADD(dd,DATEDIFF(dd,0,@EndDate)  , 0)

    --If the inputs are in the wrong order, reverse them.
     IF @StartDate > @EndDate
        SELECT @Swap      = @EndDate,
               @EndDate   = @StartDate,
               @StartDate = @Swap

    --Calculate and return the number of workdays using the input parameters.
    --This is the meat of the function.
    --This is really just one formula with a couple of parts that are listed on separate lines for documentation purposes.
     RETURN (
        SELECT
        --Start with total number of days including weekends
        (DATEDIFF(dd,@StartDate, @EndDate)+1)
        --Subtact 2 days for each full weekend
        -(DATEDIFF(wk,@StartDate, @EndDate)*2)
        --If StartDate is a Sunday, Subtract 1
        -(CASE WHEN DATENAME(dw, @StartDate) = 'Sunday'
            THEN 1
            ELSE 0
        END)
        --If EndDate is a Saturday, Subtract 1
        -(CASE WHEN DATENAME(dw, @EndDate) = 'Saturday'
            THEN 1
            ELSE 0
        END)
        )
    END
GO

Se è necessario utilizzare un calendario personalizzato, potrebbe essere necessario aggiungere alcuni controlli e alcuni parametri. Speriamo che fornisca un buon punto di partenza.


Grazie per aver incluso il link per capire come funziona. La scrittura su sqlservercentral è stata fantastica!
Chris Porter,

20

Tutto il merito a Bogdan Maxim e Peter Mortensen. Questo è il loro post, ho appena aggiunto le festività alla funzione (questo presuppone che tu abbia una tabella "tblHolidays" con un campo datetime "HolDate".

--Changing current database to the Master database allows function to be shared by everyone.
USE MASTER
GO
--If the function already exists, drop it.
IF EXISTS
(
    SELECT *
    FROM dbo.SYSOBJECTS
    WHERE ID = OBJECT_ID(N'[dbo].[fn_WorkDays]')
    AND XType IN (N'FN', N'IF', N'TF')
)

DROP FUNCTION [dbo].[fn_WorkDays]
GO
 CREATE FUNCTION dbo.fn_WorkDays
--Presets
--Define the input parameters (OK if reversed by mistake).
(
    @StartDate DATETIME,
    @EndDate   DATETIME = NULL --@EndDate replaced by @StartDate when DEFAULTed
)

--Define the output data type.
RETURNS INT

AS
--Calculate the RETURN of the function.
BEGIN
    --Declare local variables
    --Temporarily holds @EndDate during date reversal.
    DECLARE @Swap DATETIME

    --If the Start Date is null, return a NULL and exit.
    IF @StartDate IS NULL
        RETURN NULL

    --If the End Date is null, populate with Start Date value so will have two dates (required by DATEDIFF below).
    IF @EndDate IS NULL
        SELECT @EndDate = @StartDate

    --Strip the time element from both dates (just to be safe) by converting to whole days and back to a date.
    --Usually faster than CONVERT.
    --0 is a date (01/01/1900 00:00:00.000)
    SELECT @StartDate = DATEADD(dd,DATEDIFF(dd,0,@StartDate), 0),
            @EndDate   = DATEADD(dd,DATEDIFF(dd,0,@EndDate)  , 0)

    --If the inputs are in the wrong order, reverse them.
    IF @StartDate > @EndDate
        SELECT @Swap      = @EndDate,
               @EndDate   = @StartDate,
               @StartDate = @Swap

    --Calculate and return the number of workdays using the input parameters.
    --This is the meat of the function.
    --This is really just one formula with a couple of parts that are listed on separate lines for documentation purposes.
    RETURN (
        SELECT
        --Start with total number of days including weekends
        (DATEDIFF(dd,@StartDate, @EndDate)+1)
        --Subtact 2 days for each full weekend
        -(DATEDIFF(wk,@StartDate, @EndDate)*2)
        --If StartDate is a Sunday, Subtract 1
        -(CASE WHEN DATENAME(dw, @StartDate) = 'Sunday'
            THEN 1
            ELSE 0
        END)
        --If EndDate is a Saturday, Subtract 1
        -(CASE WHEN DATENAME(dw, @EndDate) = 'Saturday'
            THEN 1
            ELSE 0
        END)
        --Subtract all holidays
        -(Select Count(*) from [DB04\DB04].[Gateway].[dbo].[tblHolidays]
          where  [HolDate] between @StartDate and @EndDate )
        )
    END  
GO
-- Test Script
/*
declare @EndDate datetime= dateadd(m,2,getdate())
print @EndDate
select  [Master].[dbo].[fn_WorkDays] (getdate(), @EndDate)
*/

2
Ciao Dan B. Solo per farti sapere che la tua versione presuppone che la tabella tblHolidays non contenga il sabato e il lunedì, cosa che a volte accade. Comunque, grazie per aver condiviso la tua versione. Saluti
Julio Nobre,

3
Julio - Sì - La mia versione presuppone che il sabato e la domenica (non il lunedì) siano i fine settimana, e quindi non il giorno "non commerciale". Ma se stai lavorando nei fine settimana, immagino che ogni giorno sia un "giorno lavorativo" e puoi commentare la parte di sabato e domenica della clausola e aggiungere tutte le tue vacanze alla tabella tblHolidays.
Dan B,

1
Grazie Dan. Ho incorporato questo nella mia funzione, aggiungendo un assegno per i fine settimana poiché la mia tabella DateDimensions include tutte le date, le festività, ecc. Prendendo la tua funzione, ho appena aggiunto: e IsWeekend = 0 dopo dove [HolDate] tra StartDate e EndDate)
AlsoKnownAsJazz

Se la tabella delle festività contiene festività durante il fine settimana, è possibile modificare i criteri in questo modo: WHERE HolDate BETWEEN @StartDate AND @EndDate AND DATEPART(dw, HolDate) BETWEEN 2 AND 6contare solo le festività dal lunedì al venerdì.
Andre

7

Un altro approccio al calcolo dei giorni lavorativi è quello di utilizzare un ciclo WHILE che sostanzialmente scorre attraverso un intervallo di date e lo incrementa di 1 ogni volta che si trova che i giorni sono compresi tra lunedì e venerdì. Lo script completo per il calcolo dei giorni lavorativi utilizzando il ciclo WHILE è mostrato di seguito:

CREATE FUNCTION [dbo].[fn_GetTotalWorkingDaysUsingLoop]
(@DateFrom DATE,
@DateTo   DATE
)
RETURNS INT
AS
     BEGIN
         DECLARE @TotWorkingDays INT= 0;
         WHILE @DateFrom <= @DateTo
             BEGIN
                 IF DATENAME(WEEKDAY, @DateFrom) IN('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday')
                     BEGIN
                         SET @TotWorkingDays = @TotWorkingDays + 1;
                 END;
                 SET @DateFrom = DATEADD(DAY, 1, @DateFrom);
             END;
         RETURN @TotWorkingDays;
     END;
GO

Sebbene l'opzione ciclo WHILE sia più pulita e utilizzi meno righe di codice, ha il potenziale di essere un collo di bottiglia delle prestazioni nel tuo ambiente, in particolare quando l'intervallo di date si estende su diversi anni.

Puoi vedere più metodi su come calcolare giorni e ore di lavoro in questo articolo: https://www.sqlshack.com/how-to-calculate-work-days-and-hours-in-sql-server/


6

La mia versione della risposta accettata come funzione che utilizza DATEPART, quindi non devo fare un confronto di stringhe sulla linea con

DATENAME(dw, @StartDate) = 'Sunday'

Ad ogni modo, ecco la mia funzione di datiff aziendale

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE FUNCTION BDATEDIFF
(
    @startdate as DATETIME,
    @enddate as DATETIME
)
RETURNS INT
AS
BEGIN
    DECLARE @res int

SET @res = (DATEDIFF(dd, @startdate, @enddate) + 1)
    -(DATEDIFF(wk, @startdate, @enddate) * 2)
    -(CASE WHEN DATEPART(dw, @startdate) = 1 THEN 1 ELSE 0 END)
    -(CASE WHEN DATEPART(dw, @enddate) = 7 THEN 1 ELSE 0 END)

    RETURN @res
END
GO

5
 DECLARE @TotalDays INT,@WorkDays INT
 DECLARE @ReducedDayswithEndDate INT
 DECLARE @WeekPart INT
 DECLARE @DatePart INT

 SET @TotalDays= DATEDIFF(day, @StartDate, @EndDate) +1
 SELECT @ReducedDayswithEndDate = CASE DATENAME(weekday, @EndDate)
  WHEN 'Saturday' THEN 1
  WHEN 'Sunday' THEN 2
  ELSE 0 END 
 SET @TotalDays=@TotalDays-@ReducedDayswithEndDate
 SET @WeekPart=@TotalDays/7;
 SET @DatePart=@TotalDays%7;
 SET @WorkDays=(@WeekPart*5)+@DatePart

 RETURN @WorkDays

Se si codice postale, campioni di XML o di dati, si prega di evidenziare quelle linee in editor di testo e fare clic sul pulsante "codice di campioni" ({}) nella barra degli strumenti Editor per ben formato e la sintassi evidenziarlo!
marc_s,

Ottimo, nessuna necessità di funzioni periferiche o aggiornamenti al database che utilizza questo. Grazie. Adoro il saltire a proposito :-)
Brian Scott

Super soluzione. Ho utilizzato le formule per le variabili da utilizzare in un universo webi per calcolare i giorni della settimana (MF) tra le date in 2 colonne della tabella in questo modo ... (((((DATEDIFF (day, table.col1, table.col2) +1) - ((CASE DATENAME (giorno della settimana, table.col2) QUANDO 'Sabato' THEN 1 WHEN 'Sunday' THEN 2 ELSE 0 END))) / 7) * 5) + (((DATEDIFF (day, table.col1, table.col2 ) +1) - ((CASE DATENAME (giorno della settimana, table.col2) QUANDO 'Sabato' POI 1 QUANDO 'Domenica' POI 2 ELSE 0 FINE)))% 7)
Ilario

5

(Sono alcuni punti timidi di commentare i privilegi)

Se decidi di rinunciare al +1 giorno nell'elegante soluzione di CMS, tieni presente che se la data di inizio e quella di fine si trovano nello stesso fine settimana, otterrai una risposta negativa. Vale a dire, dal 2008/10/26 al 2008/10/26 restituisce -1.

la mia soluzione piuttosto semplicistica:

select @Result = (..CMS's answer..)
if  (@Result < 0)
        select @Result = 0
    RETURN @Result

.. che azzera anche tutti i post errati con data di inizio dopo la data di fine . Qualcosa che potresti o non stai cercando.


5

Per differenza tra le date comprese le festività sono andato in questo modo:

1) Tavolo con festività:

    CREATE TABLE [dbo].[Holiday](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
[Date] [datetime] NOT NULL)

2) Ho avuto la mia tabella di pianificazioni in questo modo e volevo riempire la colonna Work_Days che era vuota:

    CREATE TABLE [dbo].[Plan_Phase](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Id_Plan] [int] NOT NULL,
[Id_Phase] [int] NOT NULL,
[Start_Date] [datetime] NULL,
[End_Date] [datetime] NULL,
[Work_Days] [int] NULL)

3) Quindi, per far sì che "Work_Days" si riempia in seguito nella mia colonna, ho dovuto:

SELECT Start_Date, End_Date,
 (DATEDIFF(dd, Start_Date, End_Date) + 1)
-(DATEDIFF(wk, Start_Date, End_Date) * 2)
-(SELECT COUNT(*) From Holiday Where Date  >= Start_Date AND Date <= End_Date)
-(CASE WHEN DATENAME(dw, Start_Date) = 'Sunday' THEN 1 ELSE 0 END)
-(CASE WHEN DATENAME(dw, End_Date) = 'Saturday' THEN 1 ELSE 0 END)
-(CASE WHEN (SELECT COUNT(*) From Holiday Where Start_Date  = Date) > 0 THEN 1 ELSE 0 END)
-(CASE WHEN (SELECT COUNT(*) From Holiday Where End_Date  = Date) > 0 THEN 1 ELSE 0 END) AS Work_Days
from Plan_Phase

Spero di poterti aiutare.

Saluti


1
Per quanto riguarda le sottrazioni delle vacanze. Cosa succede se la data di inizio è il 1 gennaio e la data di fine è il 31 dicembre? Sottrai solo 2 - che è sbagliato. Propongo di utilizzare DATEDIFF (giorno, Start_Date, Date) e lo stesso per End_Date anziché l'intero "SELECT COUNT (*) FROM Holiday ...".
Illia Ratkevych,

4

Ecco una versione che funziona bene (penso). La tabella delle festività contiene colonne Holiday_date che contengono le festività osservate dalla tua azienda.

DECLARE @RAWDAYS INT

   SELECT @RAWDAYS =  DATEDIFF(day, @StartDate, @EndDate )--+1
                    -( 2 * DATEDIFF( week, @StartDate, @EndDate ) )
                    + CASE WHEN DATENAME(dw, @StartDate) = 'Saturday' THEN 1 ELSE 0 END
                    - CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END 

   SELECT  @RAWDAYS - COUNT(*) 
     FROM HOLIDAY NumberOfBusinessDays
    WHERE [Holiday_Date] BETWEEN @StartDate+1 AND @EndDate 

Quelle date delle vacanze potrebbero cadere anche nei fine settimana. E per alcuni, le festività di domenica saranno sostituite dal lunedì successivo.
Irawan Soetomo,

3

So che questa è una vecchia domanda, ma avevo bisogno di una formula per i giorni lavorativi esclusa la data di inizio poiché ho diversi articoli e ho bisogno che i giorni si accumulino correttamente.

Nessuna delle risposte non iterative ha funzionato per me.

Ho usato una definizione come

Numero di volte trascorse da mezzanotte a lunedì, martedì, mercoledì, giovedì e venerdì

(altri potrebbero contare da mezzanotte a sabato anziché lunedì)

Ho finito con questa formula

SELECT DATEDIFF(day, @StartDate, @EndDate) /* all midnights passed */
     - DATEDIFF(week, @StartDate, @EndDate) /* remove sunday midnights */
     - DATEDIFF(week, DATEADD(day, 1, @StartDate), DATEADD(day, 1, @EndDate)) /* remove saturday midnights */

1
Quello lo ha fatto per me, ma ho dovuto fare un piccolo cambiamento. Non contava quando @StartDateè sabato o venerdì. Ecco la mia versione:DATEDIFF(day, @StartDate, @EndDate) - DATEDIFF(week, @StartDate, @EndDate) - DATEDIFF(week, DATEADD(day, 1, @StartDate), DATEADD(day, 1, @EndDate)) - (CASE WHEN DATEPART(WEEKDAY, @StartDate) IN (1, 7) THEN 1 ELSE 0 END) + 1
caiosm1005

@ caiosm1005, da sabato a domenica ritorna 0, da sabato a lunedì ritorna 1, da venerdì a sabato ritorna 0. Tutti sono coerenti con la mia definizione. Il codice non si accumulerà correttamente (ad es. Ritorno 6 da venerdì a venerdì ma 5 da lunedì a lunedì)
adrianm,

3

Questa è sostanzialmente la risposta di CMS senza fare affidamento su una particolare impostazione della lingua. E poiché stiamo girando per generici, ciò significa che dovrebbe funzionare anche per tutte le @@datefirstimpostazioni.

datediff(day, <start>, <end>) + 1 - datediff(week, <start>, <end>) * 2
    /* if start is a Sunday, adjust by -1 */
  + case when datepart(weekday, <start>) = 8 - @@datefirst then -1 else 0 end
    /* if end is a Saturday, adjust by -1 */
  + case when datepart(weekday, <end>) = (13 - @@datefirst) % 7 + 1 then -1 else 0 end

datediff(week, ...) utilizza sempre un limite da sabato a domenica per settimane, quindi l'espressione è deterministica e non deve essere modificata (a condizione che la nostra definizione di giorni feriali sia costantemente dal lunedì al venerdì). La numerazione dei giorni varia in base al @@datefirst all'impostazione e al i calcoli modificati gestiscono questa correzione con la piccola complicazione di qualche aritmetica modulare.

Un modo più pulito di gestire la cosa di sabato / domenica è tradurre le date prima dell'estrazione del valore di un giorno della settimana. Dopo lo spostamento, i valori torneranno in linea con una numerazione fissa (e probabilmente più familiare) che inizia con 1 la domenica e termina con 7 il sabato.

datediff(day, <start>, <end>) + 1 - datediff(week, <start>, <end>) * 2
  + case when datepart(weekday, dateadd(day, @@datefirst, <start>)) = 1 then -1 else 0 end
  + case when datepart(weekday, dateadd(day, @@datefirst, <end>))   = 7 then -1 else 0 end

Ho rintracciato questa forma della soluzione almeno fino al 2002 e un articolo di Itzik Ben-Gan. ( https://technet.microsoft.com/en-us/library/aa175781(v=sql.80).aspx ) Sebbene necessitasse di una piccola modifica poiché i datetipi più recenti non consentono l'aritmetica della data, è altrimenti identico.

EDIT: ho aggiunto di nuovo quello +1che era stato in qualche modo lasciato fuori. Vale anche la pena notare che questo metodo conta sempre i giorni di inizio e fine. Presuppone inoltre che la data di fine sia successiva o successiva alla data di inizio.


Si noti che questo restituirà risultati errati per molte date nei fine settimana in modo che non aggiungano maiuscole (Ven-> Lun dovrebbe essere uguale a Ven-> Sab + Sab-> Dom + Dom-> Lun). Ven-> Sat dovrebbe essere 0 (corretto), Sat-> Sun dovrebbe essere 0 (errato -1), Sun-> Mon dovrebbe essere 1 (errato 0). Altri errori che ne conseguono sono Sab-> Sat = -1, Sun-> Sun = -1, Sun-> Sat = 4
adrianm

@adrianm Credo di aver corretto i problemi. In realtà il problema era che era sempre spento da uno perché avevo in qualche modo lasciato cadere quella parte per caso.
shawnt00

Grazie per l'aggiornamento. Pensavo che la tua formula escludesse la data di inizio, che è ciò di cui avevo bisogno. Risolto da solo e aggiunto come un'altra risposta.
Adrianm,

2

Utilizzando una tabella delle date:

    DECLARE 
        @StartDate date = '2014-01-01',
        @EndDate date = '2014-01-31'; 
    SELECT 
        COUNT(*) As NumberOfWeekDays
    FROM dbo.Calendar
    WHERE CalendarDate BETWEEN @StartDate AND @EndDate
      AND IsWorkDay = 1;

Se non lo possiedi, puoi utilizzare una tabella dei numeri:

    DECLARE 
    @StartDate datetime = '2014-01-01',
    @EndDate datetime = '2014-01-31'; 
    SELECT 
    SUM(CASE WHEN DATEPART(dw, DATEADD(dd, Number-1, @StartDate)) BETWEEN 2 AND 6 THEN 1 ELSE 0 END) As NumberOfWeekDays
    FROM dbo.Numbers
    WHERE Number <= DATEDIFF(dd, @StartDate, @EndDate) + 1 -- Number table starts at 1, we want a 0 base

Dovrebbero essere entrambi veloci ed elimina l'ambiguità / complessità. La prima opzione è la migliore, ma se non si dispone di una tabella del calendario è sempre possibile creare una tabella dei numeri con un CTE.


1
DECLARE @StartDate datetime,@EndDate datetime

select @StartDate='3/2/2010', @EndDate='3/7/2010'

DECLARE @TotalDays INT,@WorkDays INT

DECLARE @ReducedDayswithEndDate INT

DECLARE @WeekPart INT

DECLARE @DatePart INT

SET @TotalDays= DATEDIFF(day, @StartDate, @EndDate) +1

SELECT @ReducedDayswithEndDate = CASE DATENAME(weekday, @EndDate)
    WHEN 'Saturday' THEN 1
    WHEN 'Sunday' THEN 2
    ELSE 0 END

SET @TotalDays=@TotalDays-@ReducedDayswithEndDate

SET @WeekPart=@TotalDays/7;

SET @DatePart=@TotalDays%7;

SET @WorkDays=(@WeekPart*5)+@DatePart

SELECT @WorkDays

Se hai intenzione di usare una funzione, potrebbe essere meglio usare una funzione basata su tabella come nella risposta di Mário Meyrelles
James Jenkins,

1
CREATE FUNCTION x
(
    @StartDate DATETIME,
    @EndDate DATETIME
)
RETURNS INT
AS
BEGIN
    DECLARE @Teller INT

    SET @StartDate = DATEADD(dd,1,@StartDate)

    SET @Teller = 0
    IF DATEDIFF(dd,@StartDate,@EndDate) <= 0
    BEGIN
        SET @Teller = 0 
    END
    ELSE
    BEGIN
        WHILE
            DATEDIFF(dd,@StartDate,@EndDate) >= 0
        BEGIN
            IF DATEPART(dw,@StartDate) < 6
            BEGIN
                SET @Teller = @Teller + 1
            END
            SET @StartDate = DATEADD(dd,1,@StartDate)
        END
    END
    RETURN @Teller
END

1

Ho preso i vari esempi qui, ma nella mia situazione particolare abbiamo un @PromisedDate per la consegna e un @ReceivedDate per l'effettiva ricezione dell'articolo. Quando un articolo è stato ricevuto prima della "PromisedDate", i calcoli non venivano calcolati correttamente se non avevo ordinato le date passate nella funzione per ordine di calendario. Non volendo controllare le date ogni volta, ho cambiato la funzione per gestirlo per me.

Create FUNCTION [dbo].[fnGetBusinessDays]
(
 @PromiseDate date,
 @ReceivedDate date
)
RETURNS integer
AS
BEGIN
 DECLARE @days integer

 SELECT @days = 
    Case when @PromiseDate > @ReceivedDate Then
        DATEDIFF(d,@PromiseDate,@ReceivedDate) + 
        ABS(DATEDIFF(wk,@PromiseDate,@ReceivedDate)) * 2 +
        CASE 
            WHEN DATENAME(dw, @PromiseDate) <> 'Saturday' AND DATENAME(dw, @ReceivedDate) = 'Saturday' THEN 1 
            WHEN DATENAME(dw, @PromiseDate) = 'Saturday' AND DATENAME(dw, @ReceivedDate) <> 'Saturday' THEN -1 
            ELSE 0
        END +
        (Select COUNT(*) FROM CompanyHolidays 
            WHERE HolidayDate BETWEEN @ReceivedDate AND @PromiseDate 
            AND DATENAME(dw, HolidayDate) <> 'Saturday' AND DATENAME(dw, HolidayDate) <> 'Sunday')
    Else
        DATEDIFF(d,@PromiseDate,@ReceivedDate)  -
        ABS(DATEDIFF(wk,@PromiseDate,@ReceivedDate)) * 2  -
            CASE 
                WHEN DATENAME(dw, @PromiseDate) <> 'Saturday' AND DATENAME(dw, @ReceivedDate) = 'Saturday' THEN 1 
                WHEN DATENAME(dw, @PromiseDate) = 'Saturday' AND DATENAME(dw, @ReceivedDate) <> 'Saturday' THEN -1 
                ELSE 0
            END -
        (Select COUNT(*) FROM CompanyHolidays 
            WHERE HolidayDate BETWEEN @PromiseDate and @ReceivedDate 
            AND DATENAME(dw, HolidayDate) <> 'Saturday' AND DATENAME(dw, HolidayDate) <> 'Sunday')
    End


 RETURN (@days)

END

1

Se è necessario aggiungere giorni lavorativi a una determinata data, è possibile creare una funzione che dipende da una tabella del calendario, descritta di seguito:

CREATE TABLE Calendar
(
  dt SMALLDATETIME PRIMARY KEY, 
  IsWorkDay BIT
);

--fill the rows with normal days, weekends and holidays.


create function AddWorkingDays (@initialDate smalldatetime, @numberOfDays int)
    returns smalldatetime as 

    begin
        declare @result smalldatetime
        set @result = 
        (
            select t.dt from
            (
                select dt, ROW_NUMBER() over (order by dt) as daysAhead from calendar 
                where dt > @initialDate
                and IsWorkDay = 1
                ) t
            where t.daysAhead = @numberOfDays
        )

        return @result
    end

+1 Ho finito per usare una soluzione simile qui
James Jenkins,

1

Come con DATEDIFF, non considero la data di fine come parte dell'intervallo. Il numero di (ad esempio) domenica tra @StartDate e @EndDate è il numero di domeniche tra un lunedì "iniziale" e @EndDate meno il numero di domeniche tra questo lunedì "iniziale" e @StartDate. Sapendo questo, possiamo calcolare il numero di giorni lavorativi come segue:

DECLARE @StartDate DATETIME
DECLARE @EndDate DATETIME
SET @StartDate = '2018/01/01'
SET @EndDate = '2019/01/01'

SELECT DATEDIFF(Day, @StartDate, @EndDate) -- Total Days
  - (DATEDIFF(Day, 0, @EndDate)/7 - DATEDIFF(Day, 0, @StartDate)/7) -- Sundays
  - (DATEDIFF(Day, -1, @EndDate)/7 - DATEDIFF(Day, -1, @StartDate)/7) -- Saturdays

I migliori saluti!


Perfetto! Questo è quello che stavo cercando. Ringraziamenti speciali!
Phantom

0

Questo funziona per me, nel mio paese il sabato e la domenica sono giorni non lavorativi.

Per me è importante il tempo di @StartDate e @EndDate.

CREATE FUNCTION [dbo].[fnGetCountWorkingBusinessDays]
(
    @StartDate as DATETIME,
    @EndDate as DATETIME
)
RETURNS INT
AS
BEGIN
    DECLARE @res int

SET @StartDate = CASE 
    WHEN DATENAME(dw, @StartDate) = 'Saturday' THEN DATEADD(dd, 2, DATEDIFF(dd, 0, @StartDate))
    WHEN DATENAME(dw, @StartDate) = 'Sunday' THEN DATEADD(dd, 1, DATEDIFF(dd, 0, @StartDate))
    ELSE @StartDate END

SET @EndDate = CASE 
    WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN DATEADD(dd, 0, DATEDIFF(dd, 0, @EndDate))
    WHEN DATENAME(dw, @EndDate) = 'Sunday' THEN DATEADD(dd, -1, DATEDIFF(dd, 0, @EndDate))
    ELSE @EndDate END


SET @res =
    (DATEDIFF(hour, @StartDate, @EndDate) / 24)
  - (DATEDIFF(wk, @StartDate, @EndDate) * 2)

SET @res = CASE WHEN @res < 0 THEN 0 ELSE @res END

    RETURN @res
END

GO

0

Crea una funzione come:

CREATE FUNCTION dbo.fn_WorkDays(@StartDate DATETIME, @EndDate DATETIME= NULL )
RETURNS INT 
AS
BEGIN
       DECLARE @Days int
       SET @Days = 0

       IF @EndDate = NULL
              SET @EndDate = EOMONTH(@StartDate) --last date of the month

       WHILE DATEDIFF(dd,@StartDate,@EndDate) >= 0
       BEGIN
              IF DATENAME(dw, @StartDate) <> 'Saturday' 
                     and DATENAME(dw, @StartDate) <> 'Sunday' 
                     and Not ((Day(@StartDate) = 1 And Month(@StartDate) = 1)) --New Year's Day.
                     and Not ((Day(@StartDate) = 4 And Month(@StartDate) = 7)) --Independence Day.
              BEGIN
                     SET @Days = @Days + 1
              END

              SET @StartDate = DATEADD(dd,1,@StartDate)
       END

       RETURN  @Days
END

Puoi chiamare la funzione come:

select dbo.fn_WorkDays('1/1/2016', '9/25/2016')

O come:

select dbo.fn_WorkDays(StartDate, EndDate) 
from table1

0
Create Function dbo.DateDiff_WeekDays 
(
@StartDate  DateTime,
@EndDate    DateTime
)
Returns Int
As

Begin   

Declare @Result Int = 0

While   @StartDate <= @EndDate
Begin 
    If DateName(DW, @StartDate) not in ('Saturday','Sunday')
        Begin
            Set @Result = @Result +1
        End
        Set @StartDate = DateAdd(Day, +1, @StartDate)
End

Return @Result

Fine


0

Ho trovato il seguente TSQL una soluzione abbastanza elegante (non ho i permessi per eseguire le funzioni). Ho trovato gli DATEDIFFignoriDATEFIRST e volevo che il mio primo giorno della settimana fosse un lunedì. Volevo anche che il primo giorno lavorativo fosse impostato su zero e se cade un fine settimana il lunedì sarà uno zero. Questo può aiutare qualcuno che ha un requisito leggermente diverso :)

Non gestisce i giorni festivi

SET DATEFIRST 1
SELECT
,(DATEDIFF(DD,  [StartDate], [EndDate]))        
-(DATEDIFF(wk,  [StartDate], [EndDate]))        
-(DATEDIFF(wk, DATEADD(dd,-@@DATEFIRST,[StartDate]), DATEADD(dd,-@@DATEFIRST,[EndDate]))) AS [WorkingDays] 
FROM /*Your Table*/ 

0

Un approccio è quello di "camminare le date" dall'inizio alla fine insieme a un'espressione case che controlla se il giorno non è un sabato o una domenica e lo contrassegna (1 per il giorno della settimana, 0 per il fine settimana). E alla fine basta sommare i flag (sarebbe uguale al conteggio dei flag 1 poiché l'altro flag è 0) per darti il ​​numero di giorni feriali.

È possibile utilizzare un tipo di utilità GetNums (startNumber, endNumber) che genera una serie di numeri per il "ciclo" dalla data di inizio alla data di fine. Fare riferimento http://tsql.solidq.com/SourceCodes/GetNums.txt per un'implementazione. La logica può anche essere estesa per soddisfare le festività (ad esempio se si dispone di una tabella delle festività)

declare @date1 as datetime = '19900101'
declare @date2 as datetime = '19900120'

select  sum(case when DATENAME(DW,currentDate) not in ('Saturday', 'Sunday') then 1 else 0 end) as noOfWorkDays
from dbo.GetNums(0,DATEDIFF(day,@date1, @date2)-1) as Num
cross apply (select DATEADD(day,n,@date1)) as Dates(currentDate)

0

Ho preso in prestito alcune idee da altri per creare la mia soluzione. Uso il codice in linea per ignorare i fine settimana e le festività federali statunitensi. Nel mio ambiente, EndDate potrebbe essere nullo, ma non precederà mai StartDate.

CREATE FUNCTION dbo.ufn_CalculateBusinessDays(
@StartDate DATE,
@EndDate DATE = NULL)

RETURNS INT
AS

BEGIN
DECLARE @TotalBusinessDays INT = 0;
DECLARE @TestDate DATE = @StartDate;


IF @EndDate IS NULL
    RETURN NULL;

WHILE @TestDate < @EndDate
BEGIN
    DECLARE @Month INT = DATEPART(MM, @TestDate);
    DECLARE @Day INT = DATEPART(DD, @TestDate);
    DECLARE @DayOfWeek INT = DATEPART(WEEKDAY, @TestDate) - 1; --Monday = 1, Tuesday = 2, etc.
    DECLARE @DayOccurrence INT = (@Day - 1) / 7 + 1; --Nth day of month (3rd Monday, for example)

    --Increment business day counter if not a weekend or holiday
    SELECT @TotalBusinessDays += (
        SELECT CASE
            --Saturday OR Sunday
            WHEN @DayOfWeek IN (6,7) THEN 0
            --New Year's Day
            WHEN @Month = 1 AND @Day = 1 THEN 0
            --MLK Jr. Day
            WHEN @Month = 1 AND @DayOfWeek = 1 AND @DayOccurrence = 3 THEN 0
            --G. Washington's Birthday
            WHEN @Month = 2 AND @DayOfWeek = 1 AND @DayOccurrence = 3 THEN 0
            --Memorial Day
            WHEN @Month = 5 AND @DayOfWeek = 1 AND @Day BETWEEN 25 AND 31 THEN 0
            --Independence Day
            WHEN @Month = 7 AND @Day = 4 THEN 0
            --Labor Day
            WHEN @Month = 9 AND @DayOfWeek = 1 AND @DayOccurrence = 1 THEN 0
            --Columbus Day
            WHEN @Month = 10 AND @DayOfWeek = 1 AND @DayOccurrence = 2 THEN 0
            --Veterans Day
            WHEN @Month = 11 AND @Day = 11 THEN 0
            --Thanksgiving
            WHEN @Month = 11 AND @DayOfWeek = 4 AND @DayOccurrence = 4 THEN 0
            --Christmas
            WHEN @Month = 12 AND @Day = 25 THEN 0
            ELSE 1
            END AS Result);

    SET @TestDate = DATEADD(dd, 1, @TestDate);
END

RETURN @TotalBusinessDays;
END
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.