Считать рабочие дни между двумя датами - PullRequest
141 голосов
/ 31 октября 2008

Как рассчитать количество рабочих дней между двумя датами в SQL Server?

с понедельника по пятницу, и это должен быть T-SQL.

Ответы [ 22 ]

2 голосов
/ 28 июня 2016

Это в основном ответ CMS без опоры на конкретную языковую настройку. А поскольку мы снимаем для общего, это означает, что оно должно работать и для всех @@datefirst настроек.

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, ...) всегда использует границу от субботы до воскресенья в течение нескольких недель, так что выражение является детерминированным и не нуждается в изменении (при условии, что наше определение дней недели последовательно с понедельника по пятницу.) Нумерация дней меняется в соответствии с настройкой @@datefirst и модифицированными вычислениями эта поправка обрабатывается с небольшим усложнением некоторой модульной арифметики.

Более понятный способ работы с субботой / воскресеньем - это перевод дат до извлечения значения дня недели. После сдвига значения вернутся в соответствие с фиксированной (и, вероятно, более знакомой) нумерацией, которая начинается с 1 в воскресенье и заканчивается 7 в субботу.

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

Я проследил эту форму решения еще по крайней мере до 2002 года и опубликовал статью Ицик Бен-Ган. (https://technet.microsoft.com/en-us/library/aa175781(v=sql.80).aspx) Несмотря на необходимость небольшой настройки, поскольку более новые типы date не допускают арифметику дат, в остальном она идентична.

EDIT: Я добавил обратно +1, который так или иначе был оставлен. Стоит также отметить, что этот метод всегда считает дни начала и окончания. Предполагается также, что дата окончания указана в или после даты начала.

1 голос
/ 01 февраля 2013

Я взял различные примеры здесь, но в моей конкретной ситуации у нас есть @PromisedDate для доставки и @ReceivedDate для фактического получения товара. Когда элемент был получен до «PromisedDate», вычисления не суммировались правильно, если я не упорядочил даты, переданные в функцию, по календарному порядку. Не желая каждый раз проверять даты, я изменил функцию, чтобы справиться с этим для меня.

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

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


 RETURN (@days)

END
1 голос
/ 03 февраля 2012
CREATE FUNCTION x
(
    @StartDate DATETIME,
    @EndDate DATETIME
)
RETURNS INT
AS
BEGIN
    DECLARE @Teller INT

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

    SET @Teller = 0
    IF DATEDIFF(dd,@StartDate,@EndDate) <= 0
    BEGIN
        SET @Teller = 0 
    END
    ELSE
    BEGIN
        WHILE
            DATEDIFF(dd,@StartDate,@EndDate) >= 0
        BEGIN
            IF DATEPART(dw,@StartDate) < 6
            BEGIN
                SET @Teller = @Teller + 1
            END
            SET @StartDate = DATEADD(dd,1,@StartDate)
        END
    END
    RETURN @Teller
END
1 голос
/ 10 марта 2010
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
1 голос
/ 05 июля 2018

Я знаю, что это старый вопрос, но мне нужна формула для рабочих дней, исключая дату начала, так как у меня есть несколько элементов, и мне нужно, чтобы дни накапливались правильно.

Ни один из неитеративных ответов не сработал для меня.

Я использовал определение как

Количество раз с полуночи до понедельника, вторника, среды, четверга и пятницы

(другие могут считать от полуночи до субботы вместо понедельника)

Я получил эту формулу

SELECT DATEDIFF(day, @StartDate, @EndDate) /* all midnights passed */
     - DATEDIFF(week, @StartDate, @EndDate) /* remove sunday midnights */
     - DATEDIFF(week, DATEADD(day, 1, @StartDate), DATEADD(day, 1, @EndDate)) /* remove saturday midnights */
1 голос
/ 13 января 2014

Если вам нужно добавить рабочие дни к определенной дате, вы можете создать функцию, которая зависит от календарной таблицы, описанной ниже:

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
0 голосов
/ 29 июня 2018

Как и в случае с DATEDIFF, я не считаю, что дата окончания является частью интервала. Количество (например) воскресений между @StartDate и @EndDate - это количество воскресений между «начальным» понедельником и @EndDate минус количество воскресений между этим «начальным» понедельником и @StartDate. Зная это, мы можем рассчитать количество рабочих дней следующим образом:

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

С наилучшими пожеланиями!

0 голосов
/ 15 февраля 2018

Один из подходов заключается в «обходе дат» от начала до конца в сочетании с выражением регистра, которое проверяет, не является ли день субботой или воскресеньем, и помечает его (1 для дня недели, 0 для выходных). И в конце просто сложите флаги (они будут равны количеству 1-флагов, так как другой флаг равен 0), чтобы получить количество дней недели.

Вы можете использовать вспомогательную функцию типа GetNums (startNumber, endNumber), которая генерирует серию чисел для «зацикливания» от начальной даты до конечной даты. См. http://tsql.solidq.com/SourceCodes/GetNums.txt для реализации. Логика также может быть расширена для обслуживания праздничных дней (например, если у вас есть праздничный стол)

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

select  sum(case when DATENAME(DW,currentDate) not in ('Saturday', 'Sunday') then 1 else 0 end) as noOfWorkDays
from dbo.GetNums(0,DATEDIFF(day,@date1, @date2)-1) as Num
cross apply (select DATEADD(day,n,@date1)) as Dates(currentDate)
0 голосов
/ 23 августа 2017

Я нашел ниже TSQL довольно элегантное решение (у меня нет прав для запуска функций). Я обнаружил, что DATEDIFF игнорирует DATEFIRST, и я хотел, чтобы моим первым днем ​​недели был понедельник. Я также хотел, чтобы первый рабочий день был установлен на ноль, и если он выпадает на выходные, понедельник будет нулевым. Это может помочь кому-то, у кого немного другое требование :)

Не распространяется на праздничные дни

SET DATEFIRST 1
SELECT
,(DATEDIFF(DD,  [StartDate], [EndDate]))        
-(DATEDIFF(wk,  [StartDate], [EndDate]))        
-(DATEDIFF(wk, DATEADD(dd,-@@DATEFIRST,[StartDate]), DATEADD(dd,-@@DATEFIRST,[EndDate]))) AS [WorkingDays] 
FROM /*Your Table*/ 
0 голосов
/ 15 марта 2014

Это работает для меня, в моей стране суббота и воскресенье нерабочие дни.

Для меня важно время @StartDate и @ EndDate.

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

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

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


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

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

    RETURN @res
END

GO
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...