Compter les jours de travail entre deux dates

163

Comment puis-je calculer le nombre de jours de travail entre deux dates dans SQL Server?

Du lundi au vendredi et ce doit être T-SQL.

Ovidiu Pacurar
la source
5
Pouvez-vous définir les jours ouvrables? du lundi au vendredi? Hors grands jours fériés? Quel pays? Doit-il être fait en SQL?
Dave K

Réponses:

301

Pour les jours ouvrables, du lundi au vendredi, vous pouvez le faire avec un seul SELECT, comme ceci:

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)

Si vous voulez inclure des vacances, vous devez vous en sortir un peu ...

CMS
la source
3
Je viens de réaliser que ce code ne fonctionne pas toujours! J'ai essayé ceci: SET @StartDate = '28 -mar-2011 'SET @EndDate = '29 -mar-2011' la réponse l'a compté comme 2 jours
greektreat
16
@greektreat Cela fonctionne bien. C'est juste que @StartDate et @EndDate sont inclus dans le décompte. Si vous voulez que du lundi au mardi compte pour 1 jour, supprimez simplement le "+ 1" après le premier DATEDIFF. Ensuite, vous obtiendrez également ven-> sam = 0, ven-> dim = 0, ven-> lun = 1.
Joe Daley
6
Dans le prolongement de @JoeDaley. Lorsque vous supprimez le + 1 après le DATEDIFF pour exclure la date de début du décompte, vous devez également ajuster la partie CASE de celui-ci. J'ai fini par utiliser ceci: + (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 fonction de nom de données dépend des paramètres régionaux. Une solution plus robuste mais aussi plus obscure consiste à remplacer les deux dernières lignes par:-(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
Pour clarifier le commentaire de @ Sequenzia, vous supprimeriez entièrement les déclarations de cas sur dimanche, en laissant uniquement+(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

Dans Calcul des jours de travail, vous pouvez trouver un bon article sur ce sujet, mais comme vous pouvez le voir, ce n'est pas si avancé.

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

Si vous devez utiliser un calendrier personnalisé, vous devrez peut-être ajouter des vérifications et des paramètres. Espérons que cela fournira un bon point de départ.

Bogdan Maxim
la source
Merci d'avoir inclus le lien pour comprendre comment cela fonctionne. L'écriture sur sqlservercentral était super!
Chris Porter
20

Tout le crédit à Bogdan Maxim et Peter Mortensen. Ceci est leur message, je viens d'ajouter des vacances à la fonction (cela suppose que vous ayez une table "tblHolidays" avec un champ 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)
*/
Dan B
la source
2
Bonjour Dan B. Juste pour vous faire savoir que votre version suppose que la table tblHolidays ne contient pas les samedis et lundis, ce qui arrive parfois. Quoi qu'il en soit, merci de partager votre version. Cheers
Julio Nobre
3
Julio - Oui - Ma version suppose que les samedis et dimanches (et non les lundis) sont des week-ends, et donc pas des jours «non ouvrés». Mais si vous travaillez le week-end, alors je suppose que tous les jours est un "jour de travail" et vous pouvez commenter la partie samedi et dimanche de la clause et ajouter simplement toutes vos vacances au tableau tblHolidays.
Dan B
1
Merci Dan. J'ai incorporé cela dans ma fonction, en ajoutant un chèque pour les week-ends car ma table DateDimensions comprend toutes les dates, jours fériés, etc. En prenant votre fonction, je viens d'ajouter: et IsWeekend = 0 après où [HolDate] entre StartDate et EndDate)
AlsoKnownAsJazz
Si la table Vacances contient des jours fériés le week-end, vous pouvez modifier les critères comme ceci: WHERE HolDate BETWEEN @StartDate AND @EndDate AND DATEPART(dw, HolDate) BETWEEN 2 AND 6pour ne compter que les jours fériés du lundi au vendredi.
Andre
7

Une autre approche pour calculer les jours ouvrables consiste à utiliser une boucle WHILE qui effectue une itération dans une plage de dates et l'incrémente de 1 chaque fois que les jours se situent entre le lundi et le vendredi. Le script complet de calcul des jours ouvrables à l'aide de la boucle WHILE est présenté ci-dessous:

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

Bien que l'option de boucle WHILE soit plus propre et utilise moins de lignes de code, elle a le potentiel d'être un goulot d'étranglement des performances dans votre environnement, en particulier lorsque votre plage de dates s'étend sur plusieurs années.

Vous pouvez voir plus de méthodes sur la façon de calculer les jours et les heures de travail dans cet article: https://www.sqlshack.com/how-to-calculate-work-days-and-hours-in-sql-server/

AliceF
la source
6

Ma version de la réponse acceptée en tant que fonction utilisant DATEPART, donc je n'ai pas à faire une comparaison de chaînes sur la ligne avec

DATENAME(dw, @StartDate) = 'Sunday'

Bref, voici ma fonction datée de mon entreprise

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
la source
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
la source
Si vous publiez des échantillons de code, XML ou de données, veuillez mettre en évidence ces lignes dans l'éditeur de texte et cliquer sur le bouton "exemples de code" ({}) dans la barre d'outils de l'éditeur pour bien le formater et la mettre en évidence!
marc_s
Génial, pas besoin de fonctions périphériques ou de mises à jour de la base de données à l'aide de cela. Merci. Love the saltire btw :-)
Brian Scott
Super solution. J'ai sous-titré des formules pour les variables à utiliser dans un univers webi pour calculer les jours de la semaine (MF) entre les dates dans 2 colonnes de tableau comme suit ... (((DATEDIFF (day, table.col1, table.col2) +1) - ((CASE DATENAME (jour de la semaine, table.col2) WHEN 'samedi' ALORS 1 QUAND 'dimanche' PUIS 2 AUTRE 0 FIN))) / 7) * 5) + (((DATEDIFF (jour, table.col1, table.col2 ) +1) - ((CASE DATENAME (jour de la semaine, table.col2) WHEN 'samedi' ALORS 1 QUAND 'dimanche' PUIS 2 AUTRE 0 FIN)))% 7)
Hilary
5

(Je suis à quelques points timide des privilèges de commentaire)

Si vous décidez de renoncer au +1 jour dans la solution élégante de CMS , notez que si vos dates de début et de fin sont dans le même week-end, vous obtenez une réponse négative. Ie., 2008/10/26 à 2008/10/26 renvoie -1.

ma solution plutôt simpliste:

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

.. qui définit également tous les messages erronés avec une date de début après la date de fin à zéro. Quelque chose que vous recherchez ou non.

phareim
la source
5

Pour la différence entre les dates, y compris les vacances, je suis allé de cette façon:

1) Table avec jours fériés:

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

2) J'avais ma table de plannings comme celle-ci et je voulais remplir la colonne Work_Days qui était vide:

    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) Donc, pour que "Work_Days" remplisse plus tard ma colonne, il suffisait de:

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

J'espère que je pourrais aider.

À votre santé

joaopintocruz
la source
1
Concernant vos soustractions de vacances. Que faire si la date de début est le 1er janvier et la date de fin le 31 décembre? Vous ne soustrayez que 2 - ce qui est faux. Je propose d'utiliser DATEDIFF (jour, Start_Date, Date) et même pour End_Date au lieu de tout 'SELECT COUNT (*) FROM Holiday ...'.
Illia Ratkevych
4

Voici une version qui fonctionne bien (je pense). La table Holiday contient des colonnes Holiday_date qui contiennent les jours fériés que votre entreprise observe.

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 
utilisateur2733766
la source
Ces dates de vacances peuvent également tomber le week-end. Et pour certains, les vacances du dimanche seront remplacées par le lundi suivant.
Irawan Soetomo
3

Je sais que c'est une vieille question, mais j'avais besoin d'une formule pour les jours ouvrables à l'exclusion de la date de début car j'ai plusieurs éléments et j'ai besoin que les jours s'accumulent correctement.

Aucune des réponses non itératives n'a fonctionné pour moi.

J'ai utilisé une définition comme

Nombre de passages de minuit au lundi, mardi, mercredi, jeudi et vendredi

(d'autres peuvent compter de minuit à samedi au lieu de lundi)

J'ai fini avec cette 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
la source
1
Celui-là l'a fait pour moi mais j'ai dû faire un petit changement. Cela ne tenait pas compte de quand @StartDateest un samedi ou un vendredi. Voici ma 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, samedi à dimanche renvoie 0, samedi à lundi renvoie 1, vendredi à samedi renvoie 0. Tout est conforme à ma définition. Votre code ne s'accumulera pas correctement (par exemple, retournez 6 pour vendredi à vendredi mais 5 pour lundi à lundi)
adrianm
3

Il s'agit essentiellement de la réponse de CMS sans dépendre d'un paramètre de langue particulier. Et comme nous visons un générique, cela signifie qu'il devrait également fonctionner pour tous les @@datefirstparamètres.

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, ...) utilise toujours une limite du samedi au dimanche pendant des semaines, de sorte que l'expression est déterministe et n'a pas besoin d'être modifiée (tant que notre définition des jours de la semaine est systématiquement du lundi au vendredi.) La numérotation des jours varie en fonction de la @@datefirst paramètre et de la les calculs modifiés gèrent cette correction avec la petite complication d'une certaine arithmétique modulaire.

Une façon plus propre de gérer la chose samedi / dimanche consiste à traduire les dates avant d'extraire une valeur de jour de semaine. Après le décalage, les valeurs seront de nouveau alignées avec une numérotation fixe (et probablement plus familière) qui commence par 1 le dimanche et se termine par 7 le samedi.

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

J'ai retracé cette forme de solution au moins jusqu'en 2002 et un article d'Itzik Ben-Gan. ( https://technet.microsoft.com/en-us/library/aa175781(v=sql.80).aspx ) Bien qu'il ait fallu un petit ajustement depuis le plus récentdate types ne permettent pas l'arithmétique des dates, il est par ailleurs identique.

EDIT: J'ai rajouté le +1qui avait en quelque sorte été laissé de côté. Il convient également de noter que cette méthode compte toujours les jours de début et de fin. Il suppose également que la date de fin est identique ou postérieure à la date de début.

shawnt00
la source
Notez que cela renverra des résultats erronés pour de nombreuses dates du week-end afin qu'ils ne s'ajoutent pas (ven-> lun devrait être identique à ven-> sam + sam-> dim + dim-> lun). Ven-> Sam devrait être 0 (correct), Sat-> Sun devrait être 0 (faux -1), Sun-> Mon devrait être 1 (faux 0). Les autres erreurs qui en découlent sont Sam-> Sat = -1, Sun-> Sun = -1, Sun-> Sat = 4
adrianm
@adrianm Je crois que j'ai corrigé les problèmes. En fait, le problème était que c'était toujours un coup parce que j'avais en quelque sorte laissé tomber cette pièce par accident.
shawnt00
Merci pour la mise à jour. Je pensais que votre formule excluait la date de début, ce dont j'avais besoin. Je l'ai résolu moi-même et l'a ajouté comme une autre réponse.
adrianm
2

À l'aide d'une table de dates:

    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;

Si vous ne l'avez pas, vous pouvez utiliser une table de nombres:

    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

Ils devraient tous les deux être rapides et cela élimine l'ambiguïté / complexité. La première option est la meilleure, mais si vous n'avez pas de table de calendrier, vous pouvez toujours créer une table de nombres avec un CTE.

Brian
la source
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
la source
Si vous envisagez d'utiliser une fonction, il serait peut-être préférable d'utiliser une fonction basée sur une table comme dans la réponse de 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
la source
1

J'ai pris les différents exemples ici, mais dans ma situation particulière, nous avons un @PromisedDate pour la livraison et un @ReceivedDate pour la réception effective de l'article. Lorsqu'un article était reçu avant la "PromisedDate", les calculs ne totalisaient pas correctement à moins que j'aie commandé les dates passées dans la fonction par ordre calendaire. Ne voulant pas vérifier les dates à chaque fois, j'ai changé la fonction pour gérer cela pour moi.

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
la source
1

Si vous devez ajouter des jours ouvrés à une date donnée, vous pouvez créer une fonction qui dépend d'un tableau de calendrier, décrit ci-dessous:

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
la source
+1 J'ai fini par utiliser une solution similaire ici
James Jenkins
1

Comme pour DATEDIFF, je ne considère pas que la date de fin fait partie de l'intervalle. Le nombre de dimanches (par exemple) entre @StartDate et @EndDate est le nombre de dimanches entre un lundi "initial" et le @EndDate moins le nombre de dimanches entre ce lundi "initial" et le @StartDate. Sachant cela, nous pouvons calculer le nombre de jours de travail comme suit:

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

Meilleures salutations!

Wolfgang Kais
la source
Parfait! C'est ce que je cherchais. Remerciement spécial!
Phantom
0

Cela fonctionne pour moi, dans mon pays le samedi et le dimanche sont des jours non ouvrables.

Pour moi, l'heure de @StartDate et de @EndDate est importante.

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
utilisateur3424126
la source
0

Créer une fonction comme:

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

Vous pouvez appeler la fonction comme:

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

Ou comme:

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

Fin

pix1985
la source
0

J'ai trouvé le TSQL ci-dessous une solution assez élégante (je n'ai pas les autorisations pour exécuter des fonctions). J'ai trouvé les DATEDIFFignoresDATEFIRST et je voulais que mon premier jour de la semaine soit un lundi. Je voulais aussi que le premier jour ouvrable soit mis à zéro et s'il tombe un week-end, le lundi sera à zéro. Cela peut aider quelqu'un qui a une exigence légèrement différente :)

Il ne gère pas les jours fériés

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*/ 
Référence9
la source
0

Une approche consiste à «parcourir les dates» du début à la fin en conjonction avec une expression de cas qui vérifie si le jour n'est pas un samedi ou un dimanche et le signale (1 pour la semaine, 0 pour le week-end). Et à la fin, il suffit de additionner les indicateurs (ce serait égal au nombre de 1-flags car l'autre drapeau est 0) pour vous donner le nombre de jours de la semaine.

Vous pouvez utiliser un type de fonction utilitaire GetNums (startNumber, endNumber) qui génère une série de nombres pour la «boucle» de la date de début à la date de fin. Référez-vous à http://tsql.solidq.com/SourceCodes/GetNums.txt pour une implémentation. La logique peut également être étendue pour répondre aux vacances (par exemple si vous avez une table de vacances)

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)
Ombresar
la source
0

J'ai emprunté des idées à d'autres pour créer ma solution. J'utilise le code en ligne pour ignorer les week-ends et les jours fériés aux États-Unis. Dans mon environnement, EndDate peut être nul, mais il ne précédera jamais 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
la source