Zählen Sie die Arbeitstage zwischen zwei Daten

162

Wie kann ich die Anzahl der Arbeitstage zwischen zwei Daten in SQL Server berechnen?

Montag bis Freitag und es muss T-SQL sein.

Ovidiu Pacurar
quelle
5
Können Sie Arbeitstage definieren? jeden Montag bis Freitag? Ohne wichtige Feiertage? Welches Land? Muss es in SQL gemacht werden?
Dave K

Antworten:

299

Für Arbeitstage von Montag bis Freitag können Sie dies mit einem einzigen SELECT wie folgt tun:

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)

Wenn Sie Feiertage einbeziehen möchten, müssen Sie es ein wenig ausarbeiten ...

CMS
quelle
3
Ich habe gerade festgestellt, dass dieser Code nicht immer funktioniert! Ich habe es versucht: SET @StartDate = '28 -mar-2011 'SET @EndDate = '29 -mar-2011' Die Antwort zählte es als 2 Tage
greektreat
16
@greektreat Es funktioniert gut. Es ist nur so, dass sowohl @StartDate als auch @EndDate in der Zählung enthalten sind. Wenn Sie möchten, dass Montag bis Dienstag als 1 Tag zählt, entfernen Sie einfach die "+ 1" nach dem ersten DATEDIFF. Dann erhalten Sie auch Fri-> Sa = 0, Fri-> So = 0, Fri-> Mo = 1.
Joe Daley
6
Als Folge von @JoeDaley. Wenn Sie die + 1 nach dem DATEDIFF entfernen, um das Startdatum von der Zählung auszuschließen, müssen Sie auch den CASE-Teil davon anpassen. Am Ende habe ich Folgendes verwendet: + (FALL WENN DATENAME (dw, @StartDate) = 'Samstag' DANN 1 SONST 0 ENDE) - (FALL WENN DATENAME (dw, @EndDate) = 'Samstag' DANN 1 SONST 0 ENDE)
Sequenzia
7
Die Dateinamenfunktion ist vom Gebietsschema abhängig. Eine robustere, aber auch dunkelere Lösung besteht darin, die letzten beiden Zeilen durch folgende zu ersetzen:-(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
Um den Kommentar von @ Sequenzia zu verdeutlichen, würden Sie die Fallaussagen über den Sonntag vollständig ENTFERNEN und nur+(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 Berechnung der Arbeitstage finden Sie einen guten Artikel zu diesem Thema, aber wie Sie sehen, ist er nicht so weit fortgeschritten.

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

Wenn Sie einen benutzerdefinierten Kalender verwenden müssen, müssen Sie möglicherweise einige Überprüfungen und einige Parameter hinzufügen. Hoffentlich bietet es einen guten Ausgangspunkt.

Bogdan Maxim
quelle
Vielen Dank, dass Sie den Link eingefügt haben, um zu verstehen, wie dies funktioniert. Das Schreiben auf sqlservercentral war großartig!
Chris Porter
20

Alle Ehre gebührt Bogdan Maxim & Peter Mortensen. Dies ist ihr Beitrag, ich habe gerade Feiertage zur Funktion hinzugefügt (Dies setzt voraus, dass Sie eine Tabelle "tblHolidays" mit einem Datum / Uhrzeit-Feld "HolDate" haben.

--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.
quelle
2
Hallo Dan B. Nur um Sie wissen zu lassen, dass Ihre Version davon ausgeht, dass die Tabelle tblHolidays keine Samstage und Montage enthält, was manchmal vorkommt. Wie auch immer, danke, dass du deine Version geteilt hast. Prost
Julio Nobre
3
Julio - Ja - Meine Version geht davon aus, dass Samstag und Sonntag (nicht Montag) Wochenenden sind und daher kein "nicht geschäftlicher" Tag. Aber wenn Sie am Wochenende arbeiten, dann ist jeder Tag ein "Arbeitstag", und Sie können den Samstag- und Sonntagsteil der Klausel auskommentieren und einfach alle Ihre Feiertage zur Tabelle tblHolidays hinzufügen.
Dan B
1
Danke Dan. Ich habe dies in meine Funktion aufgenommen und einen Scheck für Wochenenden hinzugefügt, da meine DateDimensions-Tabelle alle Daten, Feiertage usw. enthält. Bei Ihrer Funktion habe ich gerade Folgendes hinzugefügt: und IsWeekend = 0 nach wo [HolDate] zwischen StartDate und EndDate)
AlsoKnownAsJazz
Wenn die Feiertagstabelle Feiertage an Wochenenden enthält, können Sie die Kriterien wie folgt ändern: WHERE HolDate BETWEEN @StartDate AND @EndDate AND DATEPART(dw, HolDate) BETWEEN 2 AND 6Nur Feiertage von Montag bis Freitag zählen.
Andre
7

Ein anderer Ansatz zur Berechnung von Arbeitstagen besteht darin, eine WHILE-Schleife zu verwenden, die im Wesentlichen einen Datumsbereich durchläuft und ihn um 1 erhöht, wenn festgestellt wird, dass die Tage zwischen Montag und Freitag liegen. Das vollständige Skript zur Berechnung der Arbeitstage mithilfe der WHILE-Schleife ist unten dargestellt:

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

Obwohl die Option WHILE-Schleife sauberer ist und weniger Codezeilen verwendet, kann dies zu einem Leistungsengpass in Ihrer Umgebung führen, insbesondere wenn sich Ihr Datumsbereich über mehrere Jahre erstreckt.

Weitere Methoden zur Berechnung von Arbeitstagen und -stunden finden Sie in diesem Artikel: https://www.sqlshack.com/how-to-calculate-work-days-and-hours-in-sql-server/

AliceF
quelle
6

Meine Version der akzeptierten Antwort wird als Funktion mit verwendet DATEPART, sodass ich keinen Zeichenfolgenvergleich in der Zeile mit durchführen muss

DATENAME(dw, @StartDate) = 'Sunday'

Wie auch immer, hier ist meine Business-Datediff-Funktion

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
quelle
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
quelle
Wenn Sie eine Postleitzahl, XML oder Datenproben, bitte diese Zeilen im Texteditor und klicken Sie auf den „Code - Beispiele“ Taste ({}) auf der Editor - Symbolleiste zu schön Format und die Syntax markieren Sie markieren!
marc_s
Großartig, keine Peripheriefunktionen oder Aktualisierungen der Datenbank erforderlich. Vielen Dank. Ich liebe den Saltire übrigens :-)
Brian Scott
Super Lösung. Ich habe in Formeln für Variablen eingebettet, die in einem Webi-Universum verwendet werden sollen, um Wochentage (MF) zwischen den Daten in 2 Tabellenspalten wie folgt zu berechnen ... (((((DATEDIFF (Tag, table.col1, table.col2) +1)) - ((CASE DATENAME (Wochentag, table.col2) WENN 'Samstag' DANN 1 WENN 'Sonntag' DANN 2 SONST 0 ENDE)) / 7) * 5) + (((DATEDIFF (Tag, table.col1, table.col2)))) ) +1) - ((CASE DATENAME (Wochentag, table.col2) WENN 'Samstag' DANN 1 WENN 'Sonntag' DANN 2 SONST 0 ENDE))% 7)
Hilary
5

(Ich bin ein paar Punkte schüchtern, um Privilegien zu kommentieren)

Wenn Sie in der eleganten Lösung von CMS auf den Tag +1 verzichten möchten , beachten Sie, dass Sie eine negative Antwort erhalten, wenn Start- und Enddatum am selben Wochenende liegen. Das heißt, 2008/10/26 bis 2008/10/26 geben -1 zurück.

meine eher vereinfachende Lösung:

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

.. wodurch auch alle fehlerhaften Beiträge mit Startdatum nach Enddatum auf Null gesetzt werden. Etwas, nach dem Sie vielleicht suchen oder nicht.

Phareim
quelle
5

Für den Unterschied zwischen Daten einschließlich Feiertagen ging ich diesen Weg:

1) Tabelle mit Feiertagen:

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

2) Ich hatte meine Planungstabelle wie diese und wollte die Spalte Work_Days füllen, die leer war:

    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) Um "Work_Days" später zum Ausfüllen meiner Spalte zu bekommen, musste ich nur:

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

Hoffe ich konnte helfen.

Prost

Joaopintocruz
quelle
1
In Bezug auf Ihre Urlaubsabzüge. Was ist, wenn das Startdatum der 1. Januar und das Enddatum der 31. Dezember ist? Sie werden nur 2 subtrahieren - was falsch ist. Ich schlage vor, DATEDIFF (Tag, Start_Datum, Datum) und dasselbe für End_Date anstelle von 'SELECT COUNT (*) FROM Holiday ...' zu verwenden.
Illia Ratkevych
4

Hier ist eine Version, die gut funktioniert (glaube ich). Die Feiertagstabelle enthält Holiday_date-Spalten, die die von Ihrem Unternehmen beobachteten Feiertage enthalten.

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
quelle
Diese Feiertagstermine können auch auf Wochenenden fallen. Und für einige wird der Feiertag am Sonntag durch den nächsten Montag ersetzt.
Irawan Soetomo
3

Ich weiß, dass dies eine alte Frage ist, aber ich brauchte eine Formel für Arbeitstage ohne Startdatum, da ich mehrere Elemente habe und die Tage benötigt, um richtig zu akkumulieren.

Keine der nicht iterativen Antworten hat bei mir funktioniert.

Ich habe eine Definition wie verwendet

Häufigkeit, mit der Mitternacht bis Montag, Dienstag, Mittwoch, Donnerstag und Freitag vergangen sind

(andere zählen vielleicht Mitternacht bis Samstag statt Montag)

Am Ende hatte ich diese Formel

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
quelle
1
Dieser hat es für mich getan, aber ich musste eine kleine Änderung vornehmen. Es wurde nicht berücksichtigt, wann @StartDateein Samstag oder Freitag ist. Hier ist meine Version: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, Samstag bis Sonntag gibt 0 zurück, Samstag bis Montag gibt 1 zurück, Freitag bis Samstag gibt 0 zurück. Alle stimmen mit meiner Definition überein. Ihr Code wird nicht korrekt akkumuliert (z. B. 6 für Freitag bis Freitag, aber 5 für Montag bis Montag)
Adrianm
3

Dies ist im Grunde die Antwort von CMS, ohne sich auf eine bestimmte Spracheinstellung verlassen zu müssen. Und da wir für Generika fotografieren, sollte dies auch für alle @@datefirstEinstellungen funktionieren .

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, ...)Verwendet wochenlang immer eine Grenze von Samstag zu Sonntag, sodass der Ausdruck deterministisch ist und nicht geändert werden muss (solange unsere Definition von Wochentagen von Montag bis Freitag konsistent ist). Die Nummerierung der Tage variiert je nach @@datefirstEinstellung und Einstellung modifizierte Berechnungen behandeln diese Korrektur mit der kleinen Komplikation einer modularen Arithmetik.

Eine sauberere Möglichkeit, mit der Sache am Samstag / Sonntag umzugehen, besteht darin, die Daten vor dem Extrahieren eines Wochentagswerts zu übersetzen. Nach dem Verschieben stimmen die Werte wieder mit einer festen (und wahrscheinlich bekannteren) Nummerierung überein, die am Sonntag mit 1 beginnt und am Samstag mit 7 endet.

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

Ich habe diese Form der Lösung mindestens bis 2002 und einen Artikel von Itzik Ben-Gan zurückverfolgt. ( https://technet.microsoft.com/en-us/library/aa175781(v=sql.80).aspx ) Obwohl eine kleine Änderung erforderlich war, da neuere dateTypen keine Datumsarithmetik zulassen, ist sie ansonsten identisch.

EDIT: Ich habe das hinzugefügt +1, was irgendwie weggelassen wurde. Es ist auch erwähnenswert, dass diese Methode immer die Start- und Endtage zählt. Es wird auch davon ausgegangen, dass das Enddatum am oder nach dem Startdatum liegt.

shawnt00
quelle
Beachten Sie, dass dies für viele Daten an Wochenenden zu falschen Ergebnissen führt, sodass sie nicht addiert werden (Fri-> Mo sollte mit Fri-> Sa + Sa-> So + So-> Mo identisch sein). Fri-> Sat sollte 0 sein (richtig), Sat-> Sun sollte 0 sein (falsch -1), Sun-> Mon sollte 1 sein (falsch 0). Andere Fehler, die sich daraus ergeben, sind Sat-> Sat = -1, Sun-> Sun = -1, Sun-> Sat = 4
Adrianm
@adrianm Ich glaube, ich hatte die Probleme behoben. Eigentlich war das Problem, dass es immer um eins war, weil ich diesen Teil versehentlich fallen gelassen hatte.
shawnt00
Danke für das Update. Ich dachte, Ihre Formel schließt das Startdatum aus, was ich brauchte. Ich habe es selbst gelöst und als weitere Antwort hinzugefügt.
Adrianm
2

Verwenden einer Datumstabelle:

    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;

Wenn Sie das nicht haben, können Sie eine Zahlentabelle verwenden:

    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

Sie sollten beide schnell sein und die Mehrdeutigkeit / Komplexität beseitigen. Die erste Option ist die beste, aber wenn Sie keine Kalendertabelle haben, können Sie immer eine Zahlentabelle mit einem CTE erstellen.

Brian
quelle
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
quelle
Wenn Sie eine Funktion verwenden möchten
James Jenkins vom
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
quelle
1

Ich habe die verschiedenen Beispiele hier genommen, aber in meiner speziellen Situation haben wir ein @PromisedDate für die Lieferung und ein @ReceivedDate für den tatsächlichen Empfang des Artikels. Wenn ein Artikel vor dem "PromisedDate" eingegangen ist, wurden die Berechnungen nicht korrekt summiert, es sei denn, ich habe die in die Funktion übergebenen Daten in Kalenderreihenfolge bestellt. Ich wollte die Daten nicht jedes Mal überprüfen und habe die Funktion geändert, um dies für mich zu erledigen.

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
RobertD
quelle
1

Wenn Sie einem bestimmten Datum Arbeitstage hinzufügen müssen, können Sie eine Funktion erstellen, die von einer unten beschriebenen Kalendertabelle abhängt:

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
quelle
+1 Ich habe hier eine ähnliche Lösung verwendet
James Jenkins
1

Wie bei DATEDIFF betrachte ich das Enddatum nicht als Teil des Intervalls. Die Anzahl der (zum Beispiel) Sonntage zwischen @StartDate und @EndDate ist die Anzahl der Sonntage zwischen einem "anfänglichen" Montag und dem @EndDate abzüglich der Anzahl der Sonntage zwischen diesem "anfänglichen" Montag und dem @StartDate. Wenn wir das wissen, können wir die Anzahl der Arbeitstage wie folgt berechnen:

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

Freundliche Grüße!

Wolfgang Kais
quelle
Perfekt! Das habe ich gesucht. Besonderer Dank!
Phantom
0

Das funktioniert für mich, in meinem Land sind Samstag und Sonntag arbeitsfreie Tage.

Für mich ist die Zeit von @StartDate und @EndDate wichtig.

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

Erstellen Sie eine Funktion wie:

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

Sie können die Funktion wie folgt aufrufen:

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

Oder wie:

select dbo.fn_WorkDays(StartDate, EndDate) 
from table1
Igor Krupitsky
quelle
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

Ende

pix1985
quelle
0

Ich fand die folgende TSQL eine ziemlich elegante Lösung (ich habe keine Berechtigung zum Ausführen von Funktionen). Ich fand die DATEDIFFIgnoranten DATEFIRSTund wollte, dass mein erster Tag der Woche ein Montag ist. Ich wollte auch, dass der erste Arbeitstag auf Null gesetzt wird und wenn er auf ein Wochenende fällt, wird Montag eine Null sein. Dies kann jemandem helfen, der eine etwas andere Anforderung hat :)

Es werden keine Feiertage behandelt

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*/ 
Baseline9
quelle
0

Ein Ansatz besteht darin, die Daten von Anfang bis Ende in Verbindung mit einem Fallausdruck zu durchlaufen, der prüft, ob der Tag kein Samstag oder Sonntag ist, und ihn markiert (1 für Wochentag, 0 für Wochenende). Und am Ende summieren Sie einfach die Flags (dies entspricht der Anzahl der 1-Flags, da das andere Flag 0 ist), um die Anzahl der Wochentage anzugeben.

Sie können eine Dienstprogrammfunktion vom Typ GetNums (startNumber, endNumber) verwenden, die eine Reihe von Zahlen für die Schleife vom Startdatum bis zum Enddatum generiert. Eine Implementierung finden Sie unter http://tsql.solidq.com/SourceCodes/GetNums.txt . Die Logik kann auch erweitert werden, um Feiertage zu berücksichtigen (z. B. wenn Sie eine Feiertagstabelle haben).

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

Ich habe einige Ideen von anderen ausgeliehen, um meine Lösung zu erstellen. Ich verwende Inline-Code, um Wochenenden und US-Bundesfeiertage zu ignorieren. In meiner Umgebung ist EndDate möglicherweise null, steht jedoch niemals vor 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
quelle