Policz dni robocze między dwiema datami

163

Jak obliczyć liczbę dni roboczych między dwiema datami w programie SQL Server?

Od poniedziałku do piątku i musi to być T-SQL.

Ovidiu Pacurar
źródło
5
Czy potrafisz zdefiniować dni robocze? od poniedziałku do piątku? Z wyłączeniem głównych świąt? Jaki kraj? Czy trzeba to zrobić w języku SQL?
Dave K

Odpowiedzi:

301

W dni robocze, od poniedziałku do piątku, możesz to zrobić za pomocą pojedynczego WYBORU, na przykład:

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)

Jeśli chcesz uwzględnić święta, musisz trochę popracować ...

CMS
źródło
3
Właśnie zdałem sobie sprawę, że ten kod nie zawsze działa! próbowałem tego: SET @StartDate = '28 -mar-2011 'SET @EndDate = '29 -mar-2011' odpowiedź liczyła to jako 2 dni
greektreat
16
@greektreat Działa dobrze. Po prostu zarówno @StartDate, jak i @EndDate są uwzględnione w liczbie. Jeśli chcesz, aby od poniedziałku do wtorku liczył się jako 1 dzień, po prostu usuń „+ 1” po pierwszym DATEDIFF. Wtedy otrzymasz również piątek -> sob = 0, piątek -> nie = 0, piątek -> poniedziałek = 1.
Joe Daley
6
Jako kontynuacja @JoeDaley. Kiedy usuniesz + 1 po DATEDIFF, aby wykluczyć datę początkową z zliczania, musisz również dostosować część CASE. Skończyło się na tym: + (CASE WHEN DATENAME (dw, @StartDate) = 'Saturday' THEN 1 ELSE 0 END) - (CASE WHEN DATENAME (dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)
Sequenzia
7
Funkcja datename jest zależna od ustawień regionalnych. Bardziej solidnym, ale też mniej zrozumiałym rozwiązaniem jest zastąpienie dwóch ostatnich wierszy przez:-(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
Aby wyjaśnić komentarz @ Sequenzia, należy całkowicie USUNĄĆ opisy przypadków dotyczące niedzieli, pozostawiając tylko+(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

W rozdziale Obliczanie dni pracy można znaleźć dobry artykuł na ten temat, ale jak widać nie jest on aż tak zaawansowany.

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

Jeśli chcesz użyć kalendarza niestandardowego, może być konieczne dodanie kontroli i parametrów. Miejmy nadzieję, że będzie to dobry punkt wyjścia.

Bogdan Maxim
źródło
Dziękujemy za dołączenie linku do zrozumienia, jak to działa. Pisanie na sqlservercentral było świetne!
Chris Porter
20

Wszystkie podziękowania dla Bogdana Maxima i Petera Mortensena. To jest ich post, właśnie dodałem święta do funkcji (przy założeniu, że masz tabelę „tblHolidays” z polem daty i godziny „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)
*/
Dan B.
źródło
2
Cześć Dan B. Chcę tylko poinformować, że Twoja wersja zakłada, że ​​tabela tblHolidays nie zawiera sobót i poniedziałków, co czasami się zdarza. W każdym razie, dziękuję za udostępnienie swojej wersji. Pozdrawiam
Julio Nobre
3
Julio - Tak - moja wersja zakłada, że ​​soboty i niedziele (nie poniedziałek) są weekendami, a zatem nie są dniem „wolnym od pracy”. Ale jeśli pracujesz w weekendy, myślę, że każdy dzień jest „dniem roboczym” i możesz skomentować sobotę i niedzielę części klauzuli i po prostu dodać wszystkie swoje święta do tabeli tblHolidays.
Dan B,
1
Dzięki Dan. Włączyłem to do mojej funkcji, dodając czek na weekendy, ponieważ moja tabela DateDimensions zawiera wszystkie daty, święta itp. Biorąc twoją funkcję, właśnie dodałem: i IsWeekend = 0 po gdzie [HolDate] między StartDate a EndDate)
AlsoKnownAsJazz
Jeśli tabela Święta zawiera dni wolne w weekendy, możesz zmienić kryteria w następujący sposób: WHERE HolDate BETWEEN @StartDate AND @EndDate AND DATEPART(dw, HolDate) BETWEEN 2 AND 6aby liczone były tylko dni wolne od poniedziałku do piątku.
Andre
7

Innym podejściem do obliczania dni roboczych jest użycie pętli WHILE, która w zasadzie iteruje zakres dat i zwiększa go o 1 za każdym razem, gdy znajdzie się dzień przypadający na poniedziałek - piątek. Pełny skrypt do obliczania dni roboczych za pomocą pętli WHILE przedstawiono poniżej:

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

Chociaż opcja pętli WHILE jest czystsza i wykorzystuje mniej wierszy kodu, może stanowić wąskie gardło wydajności w Twoim środowisku, szczególnie gdy zakres dat obejmuje kilka lat.

Możesz zobaczyć więcej metod obliczania dni roboczych i godzin w tym artykule: https://www.sqlshack.com/how-to-calculate-work-days-and-hours-in-sql-server/

AliceF
źródło
6

Moja wersja zaakceptowanej odpowiedzi jako funkcji używającej DATEPART, więc nie muszę robić porównania ciągów w linii z

DATENAME(dw, @StartDate) = 'Sunday'

W każdym razie, oto moja funkcja z datą biznesową

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
Carter Cole
źródło
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
Muthuvel
źródło
Jeśli kod pocztowy, próbki XML lub danych, należy zaznaczyć te linie w edytorze tekstowym i kliknij na przycisk ({}) na pasku narzędzi edytora „Kod próbki”, aby ładnie format i składnia go podświetlić!
marc_s
Świetnie, nie ma potrzeby korzystania z funkcji peryferyjnych lub aktualizacji bazy danych. Dzięki. Przy okazji uwielbiam saltire :-)
Brian Scott,
Super rozwiązanie. W formułach dla zmiennych, które mają być używane we Wszechświecie webi, w celu obliczenia dni tygodnia (MF) między datami w 2 kolumnach tabeli, tak jak to ... ((((DATEDIFF (day, table.col1, table.col2) +1) - ((CASE DATENAME (dzień tygodnia, tabela.col2) GDY 'Sobota' TO 1 GDY 'Niedziela' TO 2 INNE 0 KONIEC))) / 7) * 5) + (((DATEDIFF (dzień, tabela.col1, tabela.col2) ) +1) - ((CASE DATENAME (dzień powszedni, table.col2) GDY „Sobota” NASTĘPNIE 1 GDY „Niedziela” WTEDY 2 INNY 0 KONIEC)))% 7)
Hilary
5

(O kilka punktów brakuje mi uprawnień do komentowania)

Jeśli zdecydujesz się zrezygnować z +1 dnia w eleganckim rozwiązaniu CMS , pamiętaj, że jeśli data rozpoczęcia i data zakończenia przypadają na ten sam weekend, otrzymasz odpowiedź negatywną. Tj., 2008/10/26 do 2008/10/26 zwraca -1.

moje raczej uproszczone rozwiązanie:

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

.. co również ustawia wszystkie błędne posty z datą rozpoczęcia po dacie zakończenia na zero. Coś, czego możesz szukać lub nie.

phareim
źródło
5

Dla różnicy między datami obejmującymi święta poszedłem w ten sposób:

1) Tabela ze świętami:

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

2) Miałem taką tabelę planowania i chciałem wypełnić pustą kolumnę Work_Days:

    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) Aby więc „Work_Days” później wypełnić moją kolumnę, wystarczyło:

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

Mam nadzieję, że mogę pomóc.

Twoje zdrowie

joaopintocruz
źródło
1
Jeśli chodzi o odejmowanie wakacji. Co się stanie, jeśli datą rozpoczęcia jest 1 stycznia, a datą zakończenia 31 grudnia? Odejmiesz tylko 2 - co jest złe. Proponuję użyć DATEDIFF (dzień, Data_początkowa, Data) i tego samego dla Data_końcowa zamiast całego „WYBIERZ LICZNIK (*) OD DNI ...”.
Illia Ratkevych
4

Oto wersja, która działa dobrze (tak mi się wydaje). Tabela dni wolnych zawiera kolumny Data_urlopu, które zawierają święta obchodzone w firmie.

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 
user2733766
źródło
Te dni świąteczne mogą również przypadać w weekendy. A dla niektórych niedzielne wakacje zostaną zastąpione przez następny poniedziałek.
Irawan Soetomo
3

Wiem, że to stare pytanie, ale potrzebowałem wzoru na dni robocze bez daty początkowej, ponieważ mam kilka pozycji i potrzebuję dni, aby poprawnie się zakumulować.

Żadna z nie-iteracyjnych odpowiedzi nie działała dla mnie.

Użyłem definicji podobnej

Ile razy mija północ do poniedziałku, wtorku, środy, czwartku i piątku

(inni mogą liczyć północ do soboty zamiast poniedziałku)

Skończyło się na tej formule

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 */
adrianm
źródło
1
Ten zrobił to dla mnie, ale musiałem zrobić małą zmianę. Nie uwzględniono, kiedy @StartDatejest sobota czy piątek. Oto moja wersja: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, od soboty do niedzieli zwraca 0, od soboty do poniedziałku zwraca 1, od piątku do soboty zwraca 0. Wszystkie są zgodne z moją definicją. Twój kod nie gromadzi się poprawnie (np.
Zwraca
3

To jest w zasadzie odpowiedź CMS bez polegania na konkretnym ustawieniu językowym. A ponieważ robimy zdjęcia generyczne, oznacza to, że powinno działać również dla wszystkich @@datefirstustawień.

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, ...) zawsze używa granicy od soboty do niedzieli przez tygodnie, więc wyrażenie jest deterministyczne i nie trzeba go modyfikować (o ile nasza definicja dni tygodnia jest konsekwentnie od poniedziałku do piątku). Numeracja dni różni się w zależności od @@datefirst ustawienia i zmodyfikowane obliczenia obsługują tę korektę z niewielką komplikacją związaną z arytmetyką modularną.

Czystszym sposobem radzenia sobie z sobotą / niedzielą jest przetłumaczenie dat przed wyodrębnieniem wartości dnia tygodnia. Po przesunięciu wartości wrócą do stałej (i prawdopodobnie bardziej znanej) numeracji, która zaczyna się od 1 w niedzielę i kończy na 7 w sobotę.

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

Śledziłem tę formę rozwiązania przynajmniej do 2002 roku i artykułu Itzika Ben-Gana. ( https://technet.microsoft.com/en-us/library/aa175781(v=sql.80).aspx ) Chociaż wymagało to niewielkiej zmiany, ponieważ nowsze datetypy nie pozwalają na arytmetykę dat, poza tym jest identyczny.

EDYCJA: dodałem z powrotem to +1, co zostało w jakiś sposób pominięte. Warto również zauważyć, że ta metoda zawsze liczy dzień rozpoczęcia i zakończenia. Zakłada się również, że data zakończenia przypada w dniu rozpoczęcia lub po niej.

shawnt00
źródło
Zwróć uwagę, że to zwróci błędne wyniki dla wielu dat w weekendy, więc nie sumują się one (piątek> poniedziałek powinien być taki sam jak piątek -> sobota + sobota -> niedziela + niedziela -> poniedziałek). Piątek -> sobota powinna wynosić 0 (poprawnie), sobota-> niedziela powinna wynosić 0 (źle -1), niedziela-> poniedziałek powinna wynosić 1 (źle 0). Inne błędy wynikające z tego to Sat-> Sat = -1, Sun-> Sun = -1, Sun-> Sat = 4
adrianm
@adrianm Myślę, że naprawiłem problemy. Właściwie problem polegał na tym, że zawsze był wyłączony o jeden, ponieważ w jakiś sposób przypadkowo upuściłem tę część.
shawnt00
Dziękuję za aktualizację. Myślałem, że twoja formuła wyklucza datę rozpoczęcia, czego potrzebowałem. Rozwiązałem to samodzielnie i dodałem jako kolejną odpowiedź.
adrianm
2

Korzystanie z tabeli dat:

    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;

Jeśli tego nie masz, możesz użyć tabeli liczb:

    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

Oba powinny być szybkie i eliminuje niejednoznaczność / złożoność. Pierwsza opcja jest najlepsza, ale jeśli nie masz tabeli kalendarza, zawsze możesz utworzyć tabelę liczb z CTE.

Brian
źródło
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
Muthuvel
źródło
Jeśli zamierzasz użyć funkcji, może lepiej będzie wybrać funkcję opartą na tabeli, jak w odpowiedzi 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
bel
źródło
1

Wziąłem tutaj różne przykłady, ale w mojej konkretnej sytuacji mamy @PromisedDate do dostawy i @ReceivedDate do faktycznego odbioru przedmiotu. Gdy element został odebrany przed „PromisedDate”, obliczenia nie były sumowane poprawnie, chyba że zamówiłem daty przekazane do funkcji według kolejności kalendarza. Nie chcąc za każdym razem sprawdzać dat, zmieniłem funkcję, żeby sobie z tym poradzić.

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
Robert D.
źródło
1

Jeśli potrzebujesz dodać dni robocze do podanej daty, możesz stworzyć funkcję zależną od tabeli kalendarza, opisaną poniżej:

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
Mário Meyrelles
źródło
+1 Skończyło się na użyciu podobnego rozwiązania tutaj
James Jenkins
1

Podobnie jak w przypadku DATEDIFF, nie uważam daty końcowej za część interwału. Liczba (na przykład) niedziel między @StartDate a @EndDate to liczba niedziel między „początkowym” poniedziałkiem a @EndDate minus liczba niedziel między tym „początkowym” poniedziałek a @StartDate. Wiedząc o tym, możemy obliczyć liczbę dni roboczych w następujący sposób:

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

Z poważaniem!

Wolfgang Kais
źródło
Idealny! To jest to, czego szukałem. Specjalne podziękowania!
Phantom
0

To działa dla mnie, w moim kraju w sobotę i niedzielę są dni wolne od pracy.

Dla mnie ważny jest czas @StartDate i @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
user3424126
źródło
0

Utwórz funkcję taką jak:

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

Możesz wywołać funkcję taką jak:

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

Lub jak:

select dbo.fn_WorkDays(StartDate, EndDate) 
from table1
Igor Krupitsky
źródło
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

Koniec

pix1985
źródło
0

Poniższy TSQL uznałem za dość eleganckie rozwiązanie (nie mam uprawnień do uruchamiania funkcji). Znalazłem DATEDIFFignorowania DATEFIRSTi chciałem, aby mój pierwszy dzień tygodnia był w poniedziałek. Chciałem też, aby pierwszy dzień roboczy był ustawiony na zero i jeśli wypadnie w weekend, poniedziałek będzie równy zero. Może to pomóc komuś, kto ma nieco inne wymagania :)

Nie obsługuje dni wolnych od pracy

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*/ 
Linia bazowa 9
źródło
0

Jednym ze sposobów jest „przechodzenie po datach” od początku do końca w połączeniu z wyrażeniem przypadku, które sprawdza, czy dzień nie jest sobotą lub niedzielą, i zaznacza go (1 dla dnia tygodnia, 0 dla weekendu). I na koniec wystarczy suma flag (byłaby równa liczbie 1 flag, ponieważ druga flaga to 0), aby uzyskać liczbę dni tygodnia.

Możesz użyć funkcji narzędziowej typu GetNums (startNumber, endNumber), która generuje serię liczb dla „zapętlenia” od daty początkowej do końcowej. Zobacz http://tsql.solidq.com/SourceCodes/GetNums.txt, aby zapoznać się z implementacją. Logikę można również rozszerzyć, aby obsłużyć święta (powiedzmy, jeśli masz świąteczny stół)

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)
umbersar
źródło
0

Kilka pomysłów pożyczyłem od innych, aby stworzyć swoje rozwiązanie. Używam kodu wbudowanego, aby ignorować weekendy i święta federalne w USA. W moim środowisku EndDate może mieć wartość null, ale nigdy nie będzie poprzedzać 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
Gary
źródło