web-dev-qa-db-fra.com

Ajouter des jours ouvrables à ce jour dans SQL sans boucles

J'ai actuellement une fonction dans ma base de données SQL qui ajoute un certain nombre de jours ouvrables à une date, par exemple. si vous entrez une date qui est un jeudi et ajoutez deux jours, la date du lundi suivant sera renvoyée. Je ne suis pas dérangé par les vacances, seuls les week-ends sont exclus.

Le problème est que cela est actuellement fait en utilisant une boucle while et cela semble ralentir considérablement la procédure stockée qui l'utilise lors de la génération d'une table. Est-ce que quelqu'un sait s'il existe un moyen d'effectuer ce calcul sans boucles while ni curseurs?

Juste pour information, voici la fonction actuelle:

ALTER FUNCTION [dbo].[AddWorkDaysToDate]
(   
@fromDate       datetime,
@daysToAdd      int
)
RETURNS datetime
AS
BEGIN   
DECLARE @toDate datetime
DECLARE @daysAdded integer

-- add the days, ignoring weekends (i.e. add working days)
set @daysAdded = 1
set @toDate = @fromDate

while @daysAdded <= @daysToAdd
begin
    -- add a day to the to date
    set @toDate = DateAdd(day, 1, @toDate)
    -- only move on a day if we've hit a week day
    if (DatePart(dw, @toDate) != 1) and (DatePart(dw, @toDate) != 7)
    begin
        set @daysAdded = @daysAdded + 1
    end
end

RETURN @toDate

END
30
Matt King

Cette réponse a été considérablement modifiée depuis son acceptation, car l'original était faux. Je suis cependant plus confiant dans la nouvelle requête, et cela ne dépend pas de DATEFIRST


Je pense que cela devrait couvrir:

declare @fromDate datetime
declare @daysToAdd int

select @fromDate = '20130123',@DaysToAdd = 4

declare @Saturday int
select @Saturday = DATEPART(weekday,'20130126')

;with Numbers as (
    select 0 as n union all select 1 union all select 2 union all select 3 union all select 4
), Split as (
    select @DaysToAdd%5 as PartialDays,@DaysToAdd/5 as WeeksToAdd
), WeekendCheck as (
    select WeeksToAdd,PartialDays,MAX(CASE WHEN DATEPART(weekday,DATEADD(day,n.n,@fromDate))=@Saturday THEN 1 ELSE 0 END) as HitWeekend
    from
    Split t
        left join
    Numbers n
        on
            t.PartialDays >= n.n
group by WeeksToAdd,PartialDays
)
select DATEADD(day,WeeksToAdd*7+PartialDays+CASE WHEN HitWeekend=1 THEN 2 ELSE 0 END,@fromDate)
from WeekendCheck

Nous avons divisé le temps à ajouter en un nombre de semaines et un nombre de jours en une semaine. Nous utilisons ensuite un tableau de petit nombre pour déterminer si l’ajout de ces quelques jours nous permet d’atteindre un samedi. Si c'est le cas, nous devons ajouter 2 jours de plus au total.

14

C'est mieux si quelqu'un recherche une solution TSQL. Pas de boucles, pas de tables, pas de déclarations de cas ET fonctionne avec des négatifs. Quelqu'un peut-il battre ça?

CREATE FUNCTION[dbo].[AddBusinessDays](@Date date,@n INT)
RETURNS DATE AS 
BEGIN
DECLARE @d INT;SET @d=4-SIGN(@n)*(4-DATEPART(DW,@Date));
RETURN DATEADD(D,@n+((ABS(@n)+@d-2)/5)*2*SIGN(@n)-@d/7,@Date);
END
29
ElmerMiller

En se basant sur la réponse acceptée pour cette question, la fonction définie par l'utilisateur (UDF) suivante devrait fonctionner dans tous les cas, quel que soit le paramètre défini pour @@DateFirst.

UPDATE: Comme l'indiquent les commentaires ci-dessous, cette fonction est conçue pour que le FromDate soit un jour de semaine. Le comportement n'est pas défini lorsqu'un jour de week-end est transmis en tant que FromDate.

ALTER FUNCTION [dbo].[BusinessDaysDateAdd] 
(
   @FromDate datetime,
   @DaysToAdd int
)
RETURNS datetime
AS
BEGIN
   DECLARE @Result datetime

   SET @Result = DATEADD(day, (@DaysToAdd % 5) + CASE ((@@DATEFIRST + DATEPART(weekday, @FromDate) + (@DaysToAdd % 5)) % 7)
                                                 WHEN 0 THEN 2
                                                 WHEN 1 THEN 1
                                                 ELSE 0 END, DATEADD(week, (@DaysToAdd / 5), @FromDate))

   RETURN @Result
END
7
Nate Cook

Cette réponse est basée sur @ la réponse de ElmerMiller .

Il corrige la valeur négative sur le commentaire du dimanche de @FistOfFury

Les valeurs négatives ne fonctionnent pas si la date indiquée est dimanche

Et le paramètre DATEFIRST de @Damien_The_Unbeliever

Mais celui-ci suppose un réglage DATEFIRST particulier (7), dont certains n’ont pas besoin.

Maintenant la fonction corrigée

CREATE FUNCTION[dbo].[AddBusinessDays](@Date DATE,@n INT)
RETURNS DATE AS 
BEGIN
DECLARE @d INT,@f INT,@DW INT;
SET @f=CAST(abs(1^SIGN(DATEPART(DW, @Date)-(7-@@DATEFIRST))) AS BIT)
SET @DW=DATEPART(DW,@Date)-(7-@@DATEFIRST)*(@f^1)+@@DATEFIRST*(@f&1)
SET @d=4-SIGN(@n)*(4-@DW);
RETURN DATEADD(D,@n+((ABS(@n)+(@d%(8+SIGN(@n)))-2)/5)*2*SIGN(@n)-@d/7,@Date);
END
6
Juan M. Elosegui

Avez-vous pensé à pré-renseigner une table de correspondance contenant tous les jours ouvrables (à l'aide de votre fonction), par exemple WorkingDays (int DaySequenceId, Date WorkingDate), vous pouvez ensuite utiliser cette table en sélectionnant le DaySequenceId de @fromDate et ajoutez @daysToAdd pour obtenir la nouvelle date de travail. De toute évidence, cette méthode entraîne également une charge supplémentaire liée à l’administration de la table WorkingDays, mais vous pouvez la pré-renseigner avec la plage de dates que vous attendez. L'autre inconvénient est que les dates de travail qui peuvent être calculées ne seront que celles contenues dans la table WorkingDays. 

5
bleeeah

* Je sais que c'est un vieux fil, mais j'ai trouvé quelque chose d'extrêmement utile il y a quelque temps, je l'ai modifié et je l'ai obtenu. 

select ((DATEADD(d,DATEDIFF(d,0,(DATEADD (d,2,@fromDate))),@numbOfDays)))*

Mise à jour: je suis désolé de trouver un morceau de code (dans une seule déclaration) et d'éviter d'utiliser une fonction, j'ai posté un code incorrect ici. 

Le bit mentionné ci-dessus peut être utilisé si le nombre de jours que vous ajoutez est de 7 ou moins. 

J'ai changé le code avec les paramètres requis pour une meilleure compréhension.

Quoi qu'il en soit, j'ai fini par utiliser ce que Nate Cook a mentionné ci-dessus. Et utilisé comme une seule ligne de code. (Parce que je me retiens d'utiliser des fonctions)

Le code de Nate 

select(
DATEADD(day, (@days % 5) + 
CASE ((@@DATEFIRST + DATEPART(weekday, GETDATE()) + (@days % 5)) % 7)
WHEN 0 THEN 2
WHEN 1 THEN 1
ELSE 0 END, DATEADD(week, (@days / 5), GETDATE()))
)
4
Vick
CREATE FUNCTION DateAddBusinessDays
(
    @Days int,
    @Date datetime  
)
RETURNS datetime
AS
BEGIN
    DECLARE @DayOfWeek int;

    SET @DayOfWeek = CASE 
                        WHEN @Days < 0 THEN (@@DateFirst + DATEPART(weekday, @Date) - 20) % 7
                        ELSE (@@DateFirst + DATEPART(weekday, @Date) - 2) % 7
                     END;

    IF @DayOfWeek = 6 SET @Days = @Days - 1
    ELSE IF @DayOfWeek = -6 SET @Days = @Days + 1;

    RETURN @Date + @Days + (@Days + @DayOfWeek) / 5 * 2;
END;

Cette fonction peut ajouter et soustraire des jours ouvrables, quelle que soit la valeur de @@ DATEFIRST. Pour soustraire les jours ouvrables, utilisez un nombre de jours négatif.

2
Arjen

Merci Damien pour le code. Il y avait une légère erreur dans les calculs en ce sens qu’elle n’ajoutait qu’un jour pour le dimanche et que, lorsque le nombre de jours ouvrables se croisait le week-end (sans atterrir le week-end), les deux jours supplémentaires n’étaient pas pris en compte. Voici une version modifiée du code de Damiens qui fonctionne avec la date par défaut à 7 heures. J'espère que cela vous aidera.

CREATE FUNCTION [dbo].[fn_AddBusinessDays]  
(  
    @StartDate datetime,  
    @BusinessDays int  
) 
RETURNS datetime  
AS  
BEGIN 
DECLARE @EndDate datetime

SET @EndDate = DATEADD(day, @BusinessDays%5 + 
           CASE         
        WHEN DATEPART(weekday,@StartDate) +  @BusinessDays%5 > 6 THEN 2                  
        ELSE 0 
           END,     
   DATEADD(week,@BusinessDays/5,@StartDate))    

   RETURN @EndDate
END  
GO
1
Evergreen435

C'est ce que j'utilise:

SET DATEFIRST 1;

SELECT DATEADD(dw, (**NumberToAdd**/5)*7+(**NumberToAdd** % 5) + 
(CASE WHEN DATEPART(dw,**YourDate**) + (**NumberToAdd** % 5) > 5 
THEN 2 ELSE 0 END), **YourDate**) AS IncrementedDate
FROM YourTable t

Le "SET DATEFIRST 1;" une partie est nécessaire pour définir le lundi comme premier jour de la semaine.

1
Tom Desair

C'est un vieux fil mais je viens de créer une table avec toutes les dates puis j'ai fait ceci:

SELECT Count(*) 
FROM Date_Table
WHERE [day] BETWEEN @StartDate and @EndDate
    AND DATENAME(weekday, [day]) NOT IN ('Sunday', 'Saturday')
1
user9073079

J'ai testé toutes les solutions proposées ici et aucune d'entre elles ne fonctionne ... Voici quelques scénarios de test qui ont cassé beaucoup des solutions ci-dessus ... (en supposant que samedi et dimanche soient les jours que vous excluez):

-Ajouter 0 jour à un samedi - Résultat attendu = samedi

-Ajouter 0 jour à un dimanche - Résultat attendu = dimanche

-Ajouter 1 jour au vendredi - Résultat attendu = le lundi suivant

-Ajouter 1 jour au samedi - Résultat attendu = le lundi suivant

-Ajouter 1 jour au dimanche - Résultat attendu = le lundi suivant

-Ajouter 3 jours au vendredi - Résultat attendu = le mercredi suivant

-Ajouter 5 jours au samedi - Résultat attendu = le vendredi suivant

-Ajouter 5 jours au vendredi - Résultat attendu = le vendredi suivant

-Sous soustraire 1 jour du lundi - Résultat attendu = le vendredi précédent

-Sous soustraire 1 jour du dimanche - Résultat attendu = le vendredi précédent

-Sous soustraire 1 jour du samedi - Résultat attendu = le vendredi précédent

-Sous soustraire 3 jours à partir du lundi - Résultat attendu = le mercredi précédent

-Sous soustraire 5 jours du samedi - Résultat attendu = le lundi précédent

-Sous soustraire 5 jours du lundi - Résultat attendu = le lundi précédent

Voici ce que j'ai écrit après avoir lu tout ce fil et repéré les bons éléments de logique:

CREATE FUNCTION [dbo].[BusinessDateAdd]
(
    @FromDate DATE
    ,@DaysToAdd INT
)
RETURNS DATE 
AS 
BEGIN

    --If there are no days to add or subtract, return the day that was passed in
    IF @DaysToAdd = 0 RETURN @FromDate

    DECLARE @Weeks INT
    DECLARE @DMod INT
    DECLARE @FromDateIndex INT

    --number of weeks
    SET @Weeks = @DaysToAdd/5

    --remainder of days
    SET @dmod = @DaysToAdd%5

    --Get the FromDate day of the week, this logic standardizes the @@DateFirst to Sunday = 1
    SET @FromDateIndex = (DATEPART(weekday, @FromDate) + @@DATEFIRST - 1) % 7 + 1

    /*Splitting the addition vs subtraction logic for readability*/

    --Adding business days
    IF @DaysToAdd > 0 
        BEGIN 

            --If the FromDate is on a weekend, move it to the previous Friday
            IF @FromDateIndex IN(1,7) 
                BEGIN
                    SET @FromDate = DATEADD(dd,CASE @FromDateIndex WHEN 1 THEN -2 WHEN 7 THEN -1 END,@FromDate)
                    SET @FromDateIndex = 6
                END

            SET @FromDate = DATEADD(dd, 
                CASE 
                    --If the mod goes through the weekend, add 2 days to account for it
                    WHEN 
                        ((@FromDateIndex = 3 --Tuesday
                        AND @dmod > 3) --Days until Friday
                        OR
                        (@FromDateIndex = 4  --Wednesday
                        AND @dmod > 2)--Days until Friday
                        OR 
                        (@FromDateIndex = 5 --Thursday
                        AND @dmod > 1)--Days until Friday
                        OR 
                        (@FromDateIndex = 6 --Friday
                        AND @dmod > 0))--Days until Friday
                        THEN 
                            @DMod+2 
                    --Otherwise just add the mod
                    ELSE 
                        @DMod 
                END, @FromDate)

        END

    --Subtracting business days
    IF @DaysToAdd < 0 
        BEGIN 

            --If the FromDate is on a weekend, move it to the next Monday
            IF @FromDateIndex IN(1,7) 
                BEGIN
                    SET @FromDate = DATEADD(dd,CASE @FromDateIndex WHEN 1 THEN 1 WHEN 7 THEN 2 END,@FromDate)
                    SET @FromDateIndex = 2
                END

            SET @FromDate = DATEADD(dd, 
                CASE 
                    --If the mod goes through the weekend, subtract 2 days to account for it
                    WHEN 
                        ((@FromDateIndex = 5 --Thursday
                        AND @dmod < -3) --Days until Monday
                        OR
                        (@FromDateIndex = 4  --Wednesday
                        AND @dmod < -2)--Days until Monday
                        OR 
                        (@FromDateIndex = 3 --Tuesday
                        AND @dmod < -1)--Days until Monday
                        OR 
                        (@FromDateIndex = 2 --Monday
                        AND @dmod < 0))--Days until Monday
                        THEN 
                            @DMod-2 
                    --Otherwise just subtract the mod
                    ELSE 
                        @DMod 
                END, @FromDate)

        END

    --Shift the date by the number of weeks
    SET @FromDate = DATEADD(ww,@Weeks,@FromDate)

    RETURN @FromDate

END
1
Davin C

La réponse acceptée à la question produit des résultats incorrects. Par exemple. select @fromDate = '03-11-1983', @DaysToAdd = 3 donne 03-14-1983 alors que 03-16-1983 est attendu

J'ai posté une solution de travail ici , mais par souci d'exhaustivité, je vais aussi l'ajouter ici. Si vous êtes intéressé par les détails des deux méthodes, visitez ma réponse initiale. Sinon, copiez-le simplement dans votre projet SQL et utilisez UTL_DateAddWorkingDays

Notez que ma solution ne fonctionne que si DATEFIRST est défini sur la valeur par défaut 7. 

Test Script utilisé pour tester diverses méthodes

CREATE FUNCTION [dbo].[UTL_DateAddWorkingDays]
(   
    @date datetime,
    @days int
)
RETURNS TABLE AS RETURN 
(
    SELECT 
        CASE 
            WHEN @days = 0 THEN @date
            WHEN DATEPART(dw, @date) = 1 THEN (SELECT Date FROM [dbo].[UTL_DateAddWorkingDays_Inner](DATEADD(d, 1, @date), @days - 1))
            WHEN DATEPART(dw, @date) = 7 THEN (SELECT Date FROM [dbo].[UTL_DateAddWorkingDays_Inner](DATEADD(d, 2, @date), @days - 1))
            ELSE (SELECT Date FROM [dbo].[UTL_DateAddWorkingDays_Inner](@date, @days))
        END AS Date
)

CREATE FUNCTION [dbo].[UTL_DateAddWorkingDays_Inner]
(   
    @date datetime,
    @days int
)
RETURNS TABLE AS RETURN 
(
    SELECT 
        DATEADD(d
        , (@days / 5) * 7 
          + (@days % 5) 
          + (CASE WHEN ((@days%5) + DATEPART(dw, @date)) IN (1,7,8,9,10) THEN 2 ELSE 0 END)
        , @date) AS Date
)
1
Martin Devillers

Pour développer le commentaire d'Amine et la réponse de Nate Cook ci-dessus, la solution one-liner à ceci est la suivante:

declare @DaysToAdd int , @FromDate datetime 
set @DaysToAdd=-5      --5 days prior is 3/28/14
set @FromDate='4/4/14'
select 
    DATEADD(day, (@DaysToAdd % 5) 
    +   CASE 
        WHEN ((@@DATEFIRST + DATEPART(weekday, @FromDate)) % 7 + (@DaysToAdd % 5)) > 6 THEN 2 
        ELSE 0 
        END
    , DATEADD(week, (@DaysToAdd / 5), @FromDate))

Notez que vous pouvez ajouter ou soustraire des jours pour avancer ou reculer dans le temps, respectivement.

1
FistOfFury

Je n'ai pas encore de serveur SQL pour tester, mais voici l'idée:

ALTER FUNCTION [dbo].[AddWorkDaysToDate]
(   
@fromDate       datetime,
@daysToAdd      int
)
RETURNS datetime
AS
BEGIN   
DECLARE @dw integer
DECLARE @toDate datetime

set datefirst 1
set @toDate = dateadd(day, @daysToAdd, @fromDate)
set @dw = datepart(dw, @toDate)

if @dw > 5 set @toDate = dateadd(day, 8 - @dw, @toDate)

RETURN @toDate

END
1
Clodoaldo Neto
WITH get_dates
AS
(
    SELECT getdate() AS date, 0 as DayNo 
    UNION ALL
    SELECT date + 1 AS date, case when DATEPART(DW, date + 1) IN (1,7) then DayNo else DayNo + 1 end
    FROM get_dates
    WHERE DayNo < 4
)
SELECT max(date) FROM get_dates
OPTION (MAXRECURSION 0) 
1
Deepak

Le calcul classique de l'ajout de semaines et de jours restants ne fonctionne pas bien avec l'ajout/la soustraction des jours de semaine en général. C'est pourquoi la méthode de la boucle pour ajouter des jours restants est facile à implémenter. La partie boucle de la logique est conçue pour gérer ces cas spéciaux du week-end.

Sans la boucle, vous devrez identifier les conditions spéciales pour compenser avec une sorte de compensation après l'ajout de semaines et de jours restants. La valeur de décalage dépend également de la direction du delta (positive ou négative).

En outre, vous aurez besoin d'une logique capable d'évaluer de manière cohérente si une date tombe un jour de semaine ou un week-end avec une valeur @@ DATEFIRST.

Cas d'utilisation:

  1. Delta est zéro.
  2. La date d'entrée est week-end et reste = 0.
  3. La date d'entrée est le week-end et le reste! = 0 et (la date d'entrée est le samedi et le delta est positif; ou la date d'entrée est le soleil et le delta est négatif).
  4. La date d'entrée est le jour de la semaine et l'ajustement du reste a pour effet que la date de sortie atterrit un week-end ou traverse un week-end par rapport à la date d'entrée.
  5. La date d'entrée est le jour de la semaine et l'ajustement du reste n'entraîne pas l'atterrissage de la date de sortie un week-end ni une fin de semaine par rapport à la date d'entrée.

La logique de travail principale pour trouver la date de sortie sera, w.r.t. addition/soustraction par nombre de jours:

Output = Input + (Weeks * 7) + Days

Où: weeks = int (delta/5), days = delta% 5

Cas d'utilisation n ° 1, nous retournons la date d'entrée car aucun calcul n'est nécessaire (en supposant que ce soit l'exigence). Les cas d'utilisation # 2-4 sont des cas d'utilisation spéciaux auxquels nous devons appliquer un décalage pour corriger la date de sortie. Pour # 5, aucune compensation n'est nécessaire. Par conséquent, il deviendra:

Output = Input + (Weeks * 7) + Days + Offset

Notre plage de valeurs de compensation se situe tout au plus entre 2 et -2 car il y a deux jours de week-end.

En utilisant DATEPART (), nous respecterons la règle de 0 = dimanche, 6 = samedi, 1-5 = lundi à vendredi. Nous utiliserons @@ DATEFIRST pour aider à compenser et à maintenir les valeurs avec insistance.

Script w/Commentaires:

    DECLARE 
    @date DATE = '2018-04-25',
    @n INT = 5 -- delta

IF @n != 0 -- (opposite of use case #1)
BEGIN
    DECLARE @dw TINYINT = (DATEPART(DW, @date) + @@DATEFIRST - 1) % 7,
            @offset INT = 0

    IF @dw IN (0,6) -- if start = weekend (use case #2 & #3)
    BEGIN
        IF @n > 0 -- pos delta
        BEGIN
            IF @n%5 = 0 -- weekend start, weekly adjustment only
                SELECT @offset = CASE WHEN @dw = 0 THEN -2 ELSE -1 END
            ELSE IF @dw = 6 -- sat start, offset to Sun
                SELECT @offset = 1
        END
        ELSE -- neg delta
        BEGIN
            IF @n%5 = 0 -- weekend start, weekly adjustment only
                SELECT @offset = CASE WHEN @dw = 0 THEN 1 ELSE 2 END
            ELSE IF @dw = 0 -- Sun start, offset to sat
                SELECT @offset = -1
        END
    END
    ELSE -- otherwise, start = weekday (use case #4)
    BEGIN
        DECLARE @dw2 TINYINT = (@dw + (@n%5) + 7) % 7

        -- if result is/crosses weekend
        IF @dw2 IN (0,6) OR (@n > 0 AND @dw2 < @dw) OR (@n < 0 AND @dw < @dw2)
            SET @offset = CASE WHEN @n > 0 THEN 2 ELSE -2 END
    END

    -- adjust date by weeks + remainder + offset (if @offset = 0, use case #5)
    SET @date = DATEADD(D, (@n%5) + @offset, DATEADD(D, CAST(@n/5 AS INT) * 7, @date))
END

SELECT @date

Script compacté

DECLARE
    @date DATE = '2018-04-27',
    @n INT = 3

IF @n != 0
BEGIN
    DECLARE @dw TINYINT = (DATEPART(DW, @date) + @@DATEFIRST - 1) % 7, @d INT = @n % 5
    DECLARE @dw2 TINYINT = (@dw + @d + 7) % 7

    SET @date = DATEADD(D, @d + ISNULL(CASE
        WHEN @dw IN (0,6) THEN
            CASE WHEN @n > 0
                THEN CASE WHEN @d = 0 THEN CASE WHEN @dw = 0 THEN -2 ELSE -1 END WHEN @dw = 6 THEN  1 END
                ELSE CASE WHEN @d = 0 THEN CASE WHEN @dw = 0 THEN  1 ELSE  2 END WHEN @dw = 0 THEN -1 END
            END
        WHEN @dw2 IN (0,6) OR (@n > 0 AND @dw2 < @dw) OR (@n < 0 AND @dw < @dw2) THEN
            CASE WHEN @n > 0 THEN 2 ELSE -2 END
        END, 0), DATEADD(D, (@n / 5)* 7, @date))
END

SELECT @date
0
Nosynchro

Cette fonction SQL fonctionne de manière similaire à la fonction Excel WORKDAY . J'espère que cela pourra vous aider.

CREATE FUNCTION [dbo].[BusDaysDateAdd] 
(
   @FromDate date,
   @DaysToAdd int
)
RETURNS date
AS
BEGIN
   DECLARE @Result date
   DECLARE @TempDate date
   DECLARE @Remainder int
   DECLARE @datePartValue int

   SET @TempDate = (DATEADD(week, (@DaysToAdd / 5), @FromDate))
   SET @Remainder = (@DaysToAdd % 5)
   SET @datePartValue = DATEPART(weekday, @TempDate)
   SET @Result = DATEADD(day,@Remainder + CASE WHEN @Remainder > 0 AND @datePartValue = 7 THEN 1
                                                WHEN @Remainder >= 1 AND @datePartValue = 6 THEN 2
                                                WHEN @Remainder >= 2 AND @datePartValue = 5 THEN 2
                                                WHEN @Remainder >= 3 AND @datePartValue = 4 THEN 2
                                                WHEN @Remainder >= 4 AND @datePartValue = 3 THEN 2
                                                WHEN @Remainder >= 5 AND @datePartValue = 2 THEN 2
                                                ELSE 0 END, @TempDate)
   RETURN @Result
END
GO

Référence

0

Soupir. Je ne peux pas croire qu'après toutes ces décennies, il y a toujours non: a) "DateAddWorkDays" standard dans Microsoft SQL Server (même si Microsoft a eu une fonction WorkDay dans Excel pour toujours) et b) une solution claire ici ou ailleurs Je peux trouver que gère tous les problèmes que les gens ont soulevés.

Voici une solution que j'ai développée et qui résout les problèmes suivants, et apparemment, toutes les réponses ci-dessus ici et ailleurs que j'ai pu trouver, en ont un ou plusieurs. Cela gère:

  1. Noms d'identifiant mnémoniques.
  2. Commentaires expliquant le code qui n'est pas clair.
  3. Ne vérifie pas chaque jour de travail devant être incrémenté (c.-à-d. Beaucoup moins que la complexité O(n)).
  4. Incréments de journée de travail négatifs.
  5. Permettre de laisser passer une partie de l'heure autre que 12 heures (pour que vous n'ayez pas à la décaper au préalable).
  6. Conserver la partie heure passée, le cas échéant, dans le résultat (au cas où vous auriez besoin de l'heure exacte x jours ouvrables plus tôt/plus tôt).
  7. Noms de week-end dans des langues autres que l'anglais.
  8. @@ DateFirst autres valeurs que la valeur par défaut (7 aussi appelé États-Unis).
  9. Spécification d'une liste personnalisée de jours non ouvrables du week-end.
  10. Permettre à la liste des jours non ouvrables du week-end de travailler si la date indiquée a une heure autre que celle du midi.
  11. Renvoi date-heure de début si le nombre de jours ouvrés est égal à 0 même si la date-heure de début est un jour non ouvrable.
  12. Passer au jour ouvrable précédent/suivant avant de commencer à incrémenter/décrémenter les jours ouvrés, respectivement. NOTE: Ceci diffère de la fonction WorkDay d'Excel, mais je pense que cela est plus utile et intuitif. Ex. Si vous recevez une demande/commande un jour de week-end et que vous disposez d'un SLA (délai de réponse, date de livraison) d'un jour ouvrable, vous ne devriez pas avoir à répondre/à livrer avant la fin d'un jour ouvrable complet. (quel que soit le nombre de jours non ouvrés adjacents qui le précèdent).
  13. Sauter les week-ends supplémentaires et/ou les jours de semaine chômés qui ont pu être étendus après l'ajout des jours de semaine chômés ayant été ajoutés lors de l'ajout des week-ends initiaux lors de l'ajout de # jours ouvrés seuls - et répétant jusqu'à ce qu'ils ne soient plus nécessaires.

SUGGESTIONS: Bien sûr, comme avec tout algorithme récursif, celui-ci peut être converti en un algorithme itératif (en implémentant votre propre pile, c'est-à-dire avec une table temporaire), mais je pense que les 32 niveaux d'imbrication sont bien plus que suffisants pour la grande majorité. des cas d'utilisation du monde réel. En outre, vous pouvez bien sûr le rendre plus générique/portable en passant les dates de jours de semaine qui ne fonctionnent pas sous forme de paramètre table-valué par rapport à une référence de table codée en dur. 

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

-- ===================================================================================================================================
-- Author:      Tom
-- Create date: 03/13/2017
-- Description: Add specified # of working days (+/-) to a specified date-time assuming existence of a list of non-work weekday 
--  dates (incl. holidays, weather days, utility outage days, fire days, etc.) in the 'NonWorkDayDate' Column of a 'NonWorkWeekday' 
--  Table. If specified # working days is 0, the specified date-time is returned.  Working days are not added until the specified 
--  date-time has first been incremented (+/-) to the next working day in the direction of the working days increment.
--  NOTE: Uses a forumla (vs. O(n) loop) that uses recusion whenever days incremented (incl. weekends) spans non-work weekdays.
--  !!!WARNING!!!: Will exceed SQL Server nesting level (32) if abs (# of working days) < ~1 / 32 adjacent non-working days.
-- Parameters:
--  @RefDateTime    DateTime:   Reference date-time to which to add '@WorkDaysIncrement'.
--  @WorkDaysIncrement  Int:    # of working days (+/-) to add # to the '@RefDateTime'.
-- Returns:
--  1. Result of @RefDateTime + @WorkDaysIncrement (skipping weekend and holiday dates and retaining the @RefDateTime's time).
-- ===================================================================================================================================
CREATE FUNCTION [dbo].[AddWorkDays_Recursive] 
(
    -- Add the parameters for the function here
    @RefDateTime datetime,
    @WorkDaysIncrement int
)
RETURNS DateTime
AS
BEGIN

-- If no days to increment, return passed in date-time (even if weekend day).
    if (@WorkDaysIncrement = 0) return @RefDateTime

-- Set the one-day increment used to add or subtract one calendar/work day.
    declare @OneDayIncrement int = sign(@WorkDaysIncrement)

-- Initialize # of calendar days added to 0.
    declare @DaysAdded int = 0

-- Set reference date to date (i.e. excl. time) of reference date-time.
    declare @RefDate datetime = convert
        (
            date,
            convert
            (
                varchar(10),
                @RefDateTime,
                101
            )
        ) 
    --end declare @RefDate 

-- Initialize result date to reference date
    declare @ResultDate datetime = @RefDate

-- Set U.S. Weekday # to the 1-based U.S. weekday # result date.
    declare @USWeekdayNumber tinyint = ((datepart(weekday, @ResultDate) + @@datefirst - 1) % 7) + 1 -- Sun to Sat = 1 to 7

-- If result date is now on a weekend day, set #  of weekend days increment so that we can move it +/- 1 to 2 days to next weekday.
    declare @WeekendDaysInc smallint = 
    (
        case (@USWeekdayNumber)

            when 1 then --Sunday 
                case
                    when (@OneDayIncrement > 0) then 1
                    else -2 
                end 
            --end when 1 --Sunday

            when 7 then --Saturday 
                case
                    when (@OneDayIncrement > 0) then 2
                    else -1
                end 
            --end when 7 then --Saturday 

            else 0 -- Not Weekend Day #

        end -- case (@USWeekdayNumber)
    ) -- end declare @WeekendDaysInc smallint = 

-- Increment # of calendar days added by #  of weekend days increment
    set @DaysAdded += @WeekendDaysInc

-- Increment result date by #  of weekend days increment
    set @ResultDate += @WeekendDaysInc 

-- Set # of work weeks increment to # of full 5-day increments in the # (+/-) of work days to increment.
    declare @WorkWeeksIncrement int = @WorkDaysIncrement / 5

-- Increment # of calendar days added by 7 times # of work weeks increment, i.e. to add weekday + weekend days for full weeks.
    set @DaysAdded += @WorkWeeksIncrement * 7

-- Set result date after full weeks added to reference date + # of calendar days 
    declare @AfterFullWeeksResultDate datetime = @ResultDate + @DaysAdded

-- Set # partial-work week days to # (+/-) of work days to increment left after adding full weeks.
    declare @PartialWorkWeekDays int = @WorkDaysIncrement % 5

-- Increment # of calendar days added by # partial-work week days
    set @DaysAdded += @PartialWorkWeekDays

-- Set result date after partial week added to result date after full weeks added + # partial work week days
    declare @AfterPartialWeekResultDate datetime = @AfterFullWeeksResultDate + @PartialWorkWeekDays

--Set result date to result date after partial week.
    set  @ResultDate = @AfterPartialWeekResultDate

-- Set After Full Weeks U.S. Weekday # to the 1-based U.S. weekday # result date.
    declare @AfterFullWeeksUSWeekdayNumber tinyint = 
        (
            ((datepart(weekday, @AfterFullWeeksResultDate) + @@datefirst - 1) % 7) + 1 -- Sun to Sat = 1 to 7
        )

-- Set After Partial Week U.S. Weekday # to the 1-based U.S. weekday # result date.
    declare @AfterPartialWeekUSWeekdayNumber tinyint = 
        (
            ((datepart(weekday, @AfterPartialWeekResultDate) + @@datefirst - 1) % 7) + 1 -- Sun to Sat = 1 to 7
        )

--If (incrementing and After Full Weeks U.S. Weekday # > @AfterPartialWeekUSWeekdayNumber) 
--  or (decrementing and After Full Weeks U.S. Weekday # < @AfterPartialWeekUSWeekdayNumber), increment by (+/-) 2 to account for 
--  the weekend that was spanned when partial-work week days were added.
    if 
    (
        (
            (@OneDayIncrement > 0)
            and (@AfterFullWeeksUSWeekdayNumber > @AfterPartialWeekUSWeekdayNumber)
        )
        or (
            (@OneDayIncrement < 0)
            and (@AfterFullWeeksUSWeekdayNumber < @AfterPartialWeekUSWeekdayNumber)
        )

    )
    begin
        set @WeekendDaysInc = 2 * @OneDayIncrement
        set @DaysAdded += @WeekendDaysInc
        set @ResultDate += @WeekendDaysInc
    end -- if need to increment to account for weekend spanned by partial-work week days,

-- Set U.S. Weekday # to the 1-based U.S. weekday # result date.
    set @USWeekdayNumber = ((datepart(weekday, @ResultDate) + @@datefirst - 1) % 7) + 1 -- Sun to Sat = 1 to 7

-- If result date is now on a weekend day, set #  of weekend days increment so that we can move it +/- 1 to 2 days to next weekday.
    set @WeekendDaysInc = 
    (
        case (@USWeekdayNumber)

            when 1 then --Sunday 
                case
                    when (@OneDayIncrement > 0) then 1
                    else -2 
                end 
            --end when 1 --Sunday

            when 7 then --Saturday 
                case
                    when (@OneDayIncrement > 0) then 2
                    else -1
                end 
            --end when 7 then --Saturday 

            else 0 -- Not Weekend Day #

        end -- case (@USWeekdayNumber)
    ) -- end declare @WeekendDaysInc smallint = 

-- Increment # of calendar days added by #  of weekend days increment
    set @DaysAdded += @WeekendDaysInc

-- Increment result date by #  of weekend days increment
    set @ResultDate += @WeekendDaysInc 

-- Set non-work weedays count to # Rows where NonWorkDayDate between RefDate and ResultDate (if # of work days to increment > 0), else between 
--  ResultDate and RefDate.
    declare @NonWorkWeekdaysCount int =
    (
        select count(nw.NonWorkDayDate) 
            from NonWorkWeekday as nw
            where 
                (
                    (@OneDayIncrement > 0)
                    and (nw.NonWorkDayDate between @RefDate and @ResultDate)
                )
                or (
                    (@OneDayIncrement < 0)
                    and (nw.NonWorkDayDate between @ResultDate and @RefDate)
                )
        --end select count(nw.NonWorkDayDate) from Holidate as nw 
    ) -- end declare @HolidaysSpanned int =

-- Set result date-time to reference date-time + # of calendar days added
    declare @ResultDateTime datetime = @RefDateTime + @DaysAdded 

-- Set result date-time equal to result of adding (# of holidays x one-day increment).
    set @ResultDateTime = dbo.AddWorkDays_Recursive
        (
            @ResultDateTime, -- @RefDateTime
            @NonWorkWeekdaysCount * @OneDayIncrement -- @WorkDaysIncrement
        )
    --end set @ResultDateTime = 

    -- Return the result of the function
    RETURN @ResultDateTime

END

GO
0
Tom

Je suis un peu en retard pour ce parti mais j'ai fini par écrire ma propre version, à cause des inconvénients des autres solutions. Plus précisément, cette version adresse le décompte en arrière et commence le week-end.

Une situation ambiguë peut survenir si vous ajoutez zéro jour ouvrable à une date de fin de semaine. J'ai conservé la même date, mais vous pouvez omettre ce contrôle si vous souhaitez toujours forcer le renvoi d'un jour de la semaine.

CREATE FUNCTION [dbo].[fn_AddBusinessDays]
(
    @date datetime,
    @businessDays int
)
RETURNS datetime
AS
BEGIN
    --adjust for weeks first
    declare @weeksToAdd int = @businessDays / 7
    declare @daysToAdd int = @businessDays % 7

    --if subtracting days, subtract a week then offset
    if @businessDays < 0 begin
        set @daysToAdd = @businessDays + 5
        set @weeksToAdd = @weeksToAdd - 1
    end

    --saturday becomes zero using the modulo operator
    declare @originalDayOfWeek int = datepart(dw, @date) % 7
    declare @newDayOfWeek int = datepart(dw, dateadd(d, @daysToAdd, @date)) % 7

    --special case for when beginning date is weekend
    --adding zero on a weekend keeps the same date. you can remove the <> 0 check if you want Sunday + 0 => Monday
    declare @dateOffset int = case
        when @businessDays <> 0 and @originalDayOfWeek = 0 then 2
        when @businessDays <> 0 and @originalDayOfWeek = 1 then 1
        when @businessDays <> 0 and @newDayOfWeek < @originalDayOfWeek then 2
        else 0
    end

    -- Return the result of the function
    return dateadd(d, @daysToAdd + @dateOffset, dateadd(ww, @weeksToAdd, @date))

END
0
dave

Je sais qu'il est un peu tard, quelqu'un d'autre est peut-être tombé sur ce problème ... J'ai essayé la solution ci-dessus, mais la plupart d'entre eux ne peuvent calculer les vacances.

Voici comment j'ai essayé

CREATE function [dbo].[DateAddWorkDay]
(@days int,@FromDate Date)
returns Date
as
begin
declare @result date
set @result = (
select b
from
(
    SELECT
    b,
       (DATEDIFF(dd, a, b))
      -(DATEDIFF(wk, a, b) * 2)
      -(CASE WHEN DATENAME(dw, a) = 'Sunday' THEN 1 ELSE 0 END)
      -(CASE WHEN DATENAME(dw, b) = 'Saturday' THEN 1 ELSE 0 END)
      -COUNT(o.Holiday_Date) 
      as workday
    from
    (
    select 
    @FromDate as a,
    dateadd(DAY,num +@days,@FromDate) as b
    from (select row_number() over (order by (select NULL)) as num
          from Information_Schema.columns
         ) t
    where num <= 100 
    ) dt
    left join Holiday o on o.Holiday_Date between a and b and DATENAME(dw, o.Holiday_Date) not in('Saturday','Sunday') 
    where DATENAME(dw, b) not in('Saturday','Sunday')
          and b not in (select Holiday_Date from OP_Holiday where Holiday_Date between a and b) 

    group by a,b
) du
where workday =@days 


)
return @result 
end

Où vacances est une table avec holiday_date comme référence pour les vacances

J'espère que cela peut aider quelqu'un.

0
user3785112

Pour l'Allemagne, toutes les réponses ne fonctionnent pas.

La seule fonction que j'ai testée et travaillée est une traduction d'un ancien formulaire Excel here

Set @EndDate=Dateadd(DAY,@DaysToAdd,@FromDate) +
Cast(((
         CASE WHEN 5 <= DATEPART(weekday, @FromDate)%7 
        THEN 5
         ELSE 
         DATEPART(weekday, @FromDate)%7
         END)
      -1 + @DaysToAdd )/5 
 as int) 
* 2 - 
   (Case when DAtepart(weekday, @FromDate)=6 then 1 else 0 end) 
0
Jansenbusch

Je viens de tester la réponse acceptée et de constater que cela ne fonctionne pas lorsque le dimanche est le jour de départ.

Vous devez ajouter les éléments suivants sous l'élément de ligne Select @Saturday:

SELECT @fromDate = CASE WHEN DATEPART(weekday,@fromDate) = 1 THEN DATEADD(day,1,@fromDate) ELSE @fromDate END
0
James