Порахуйте робочі дні між двома датами


163

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

З понеділка по п’ятницю, і він повинен бути T-SQL.


5
Чи можете ви визначити робочі дні? будь-якого понеділка через п’ятницю? За винятком великих свят? Яка країна? Потрібно це зробити в SQL?
Дейв К

Відповіді:


301

Для робочих днів, з понеділка по п’ятницю, ви можете це зробити за допомогою одного SELECT, наприклад:

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)

Якщо ви хочете включити свята, вам доведеться трохи опрацювати ...


3
Я щойно зрозумів, що цей код працює не завжди! Я спробував це: SET @StartDate = '28 -mar-2011 'SET @EndDate = '29 -mar-2011', відповідь прорахував це як 2 дні
greektreat

16
@greektreat Це прекрасно працює. Просто в підрахунок включені і @StartDate, і @EndDate. Якщо ви хочете, щоб з понеділка по вівторок вважатись 1 день, просто зніміть "+ 1" після першого DATEDIFF. Тоді ви також отримаєте пт-> сб = 0, пт-> сонце = 0, пт-> пн = 1.
Джо Дейлі

6
Як продовження до @JoeDaley. Якщо ви видалите +1 після DATEDIFF, щоб виключити стартовий дат із числа, вам також потрібно відкоригувати частину CASE. Я в кінцевому підсумку скористався цим: + (СЛУЧАЙ, КОГО ДАТЕНАМЕ (dw, @StartDate) = 'Субота' ТОГО 1 ELSE 0 END) - (CASE WHEN DATENAME (dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)
Sequenzia

7
Функція імені дати залежить від місцевості. Більш надійним, але й більш незрозумілим рішенням є заміна останніх двох рядків на:-(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
Щоб уточнити коментар @ Sequenzia, ви повністю ВИДАЛИТИ виклади справи про неділю, залишивши лише+(CASE WHEN DATENAME(dw, @StartDate) = 'Saturday' THEN 1 ELSE 0 END) - (CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)
Енді Раддац

32

У підрахунку робочих днів ви можете знайти хорошу статтю з цього приводу, але, як ви бачите, вона не така вдосконалена.

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

Якщо вам потрібно скористатися спеціальним календарем, можливо, вам доведеться додати деякі чеки та деякі параметри. Сподіваємось, це дасть хороший вихідний пункт.


Дякуємо, що включили посилання, щоб зрозуміти, як це працює. Писати на sqlservercentral було чудово!
Кріс Портер

20

Вся заслуга Богдана Максима та Пітера Мортенсена. Це їх посада, я щойно додав до функції канікули (Це передбачає, що у вас є таблиця "tblHolidays" із полем дати "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)
*/

2
Привіт, Дане Б. Просто щоб повідомити, що ваша версія передбачає, що таблиця tblHolidays не містить суботи та понеділка, що, іноді, буває. У будь-якому випадку, дякую, що поділилися вашою версією. Ура
Хуліо Нобре

3
Хуліо - Так - Моя версія передбачає, що субота та неділя (не понеділок) - вихідні дні, а тому не "неробочий" день. Але якщо ви працюєте у вихідні дні, то, мабуть, щодня - це "робочий день", і ви можете прокоментувати частину пункту суботу та неділю і просто додати всі свої свята до столу tblHolidays.
Дан Б

1
Дякую Ден. Я включив це у свою функцію, додавши чек на вихідні, оскільки моя таблиця DateDimensions включає всі дати, свята тощо. Приймаючи вашу функцію, я щойно додав: і IsWeekend = 0 після чого [HolDate] між StartDate та EndDate)
ТакожK PoznaAsJazz

Якщо святковий стіл містить свята у вихідні дні, ви можете змінити такі критерії: WHERE HolDate BETWEEN @StartDate AND @EndDate AND DATEPART(dw, HolDate) BETWEEN 2 AND 6рахувати лише свята з понеділка по п’ятницю.
Andre Andre

7

Іншим підходом до обчислення робочих днів є використання циклу WHILE, який, в основному, повторюється через діапазон дат і збільшується на 1 раз, коли дні виявляються протягом понеділка - п’ятниці. Повний сценарій для обчислення робочих днів за допомогою циклу WHILE показаний нижче:

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

Хоча параметр циклу WHILE є більш чистим і використовує менше рядків коду, він може бути вузьким місцем у вашому середовищі, особливо коли діапазон дат охоплює кілька років.

Більше методів розрахунку робочих днів і годин ви можете побачити в цій статті: https://www.sqlshack.com/how-to-calculate-work-days-and-hours-in-sql-server/


6

Моя версія прийнятої відповіді як функції, що використовує DATEPART, тому мені не доведеться робити порівняння рядків у рядку з

DATENAME(dw, @StartDate) = 'Sunday'

У всякому разі, ось моя функція датування роботи

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

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

Якщо ви розміщуєте код, XML або зразки даних, будь-ласка, виділіть ці рядки в текстовому редакторі та натисніть кнопку "зразки коду" ({}) на панелі інструментів редактора, щоб гарненько відформатувати та підкреслити синтаксис!
marc_s

Чудово, немає необхідності в периферійних функціях або оновленнях бази даних, використовуючи це. Дякую. Полюбіть салону btw :-)
Брайан Скотт

Супер рішення. Я переклав у формули для змінних, які використовуватимуться у Всесвіті Вебі, щоб обчислити будні дні (MF) між датами в 2 стовпцях таблиці так ... ((((DATEDIFF (день, table.col1, table.col2) +1) - ((DATENAME CASE (буд. День, table.col2) КОЛИ "Субота" THEN 1 WHEN "Sunday" THEN 2 ELSE 0 END))) / 7) * 5) + (((DATEDIFF (день, table.col1, table.col2) ) +1) - ((ДАТЕНА СЛУЧАЙ (буд. День, стіл.col2) КОЛИ "Субота" ТОГО 1 КОЛИ "
Хіларі

5

(Я декілька пунктів соромлюсь коментувати пільги)

Якщо ви вирішите відмовитись від +1 дня в елегантному рішенні CMS , зауважте, що якщо ваша дата початку та дата закінчення є одними вихідними, ви отримаєте негативну відповідь. Тобто, 2008/10/26 до 2008/10/26 повертається -1.

моє досить спрощене рішення:

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

.. який також встановлює нулі всі помилкові повідомлення з датою початку після дати закінчення . Щось, що ви можете або не можете шукати.


5

Для різниці між датами, включаючи свята, я пішов таким чином:

1) Стіл зі святами:

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

2) У мене була така таблиця планування, як це, і я хотів заповнити стовпчик Work_Days, який був порожнім:

    CREATE TABLE [dbo].[Plan_Phase](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Id_Plan] [int] NOT NULL,
[Id_Phase] [int] NOT NULL,
[Start_Date] [datetime] NULL,
[End_Date] [datetime] NULL,
[Work_Days] [int] NULL)

3) Отже, щоб згодом "Work_Days" згодом заповнити мою колонку, потрібно було:

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

Сподіваюся, що я можу допомогти.

Ура


1
Щодо віднімання відпусток. Що робити, якщо дата початку - 1 січня, а дата закінчення - 31 грудня? Ви віднімете лише 2 - що неправильно. Я пропоную використовувати DATEDIFF (день, Start_Date, Дата) та ж для End_Date замість цілого 'SELECT COUNT (*) OF Holiday ...'.
Ілля Раткевич

4

Ось версія, яка добре працює (я думаю). Святкова таблиця містить стовпці Holiday_date, які містять свята, які дотримується ваша компанія.

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 

Ці дати свята можуть припадати і на вихідні. А для деяких свято в неділю буде замінено наступним понеділком.
Irawan Soetomo

3

Я знаю, що це старе питання, але мені потрібна була формула для робочих днів, виключаючи дату початку, оскільки у мене є кілька пунктів і потрібні дні, щоб правильно накопичитись.

Жодна з неітеративних відповідей не працювала для мене.

Я використовував таке визначення, як

Кількість разів проходить опівночі на понеділок, вівторок, середу, четвер і п'ятницю

(інші можуть вважати півночі до суботи замість понеділка)

Я закінчив цю формулу

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
Цей зробив це для мене, але мені довелося зробити невелику зміну. Це не було рахунку, коли @StartDateсубота чи п’ятниця. Ось моя версія: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, з суботи на неділю повертається 0, з суботи на понеділок повертається 1, у п’ятницю до суботи повертається 0. Усі відповідають моєму визначенню. Ваш код не буде накопичуватися правильно (наприклад, поверніть 6 на п’ятницю до п’ятниці, але 5 - на понеділок до понеділка)
adrianm

3

Це в основному відповідь 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що якось було закінчено. Також варто зазначити, що цей метод завжди рахує дні початку та кінця. Він також передбачає, що дата закінчення настає на дату початку або після неї.


Зауважте, що це призведе до неправильних результатів для багатьох дат у вихідні дні, тому вони не додаватимуть (Пт-> Пн повинен бути таким же, як Пт-> Сб + Сб-> Сонце + Нд-> Пн). Пт-> Сб повинен бути 0 (правильно), Сб-> Сонце має бути 0 (неправильно -1), Нд-> Пн повинен бути 1 (неправильно 0). Інші помилки, що випливають із цього, - Sat-> Sat = -1, Sun-> Sun = -1, Sun-> Sat = 4
adrianm

@adrianm Я вважаю, що я виправив проблеми. Насправді проблема полягала в тому, що її завжди відключав один, бо я якось випадково скинув цю частину.
shawnt00

Дякуємо за оновлення. Я думав, що ваша формула виключає дату початку, що саме мені потрібно. Я сам це вирішив і додав як ще одну відповідь.
Адріанм

2

Використання таблиці дати:

    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;

Якщо у вас цього немає, ви можете використовувати таблицю цифр:

    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

Вони повинні бути швидкими, і це виключає неоднозначність / складність. Перший варіант є найкращим, але якщо у вас немає календарної таблиці, ви завжди можете створити таблицю цифр із CTE.


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

Якщо ви збираєтеся використовувати функцію, можливо, буде краще перейти з функцією на основі таблиці, як у відповіді Маріо Мейрелса
Джеймс Дженкінс

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

1

Тут я взяв різні приклади, але в моїй конкретній ситуації у нас є @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

Якщо вам потрібно додати робочі дні до заданої дати, ви можете створити функцію, яка залежить від календарної таблиці, описаної нижче:

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

+1 Я в кінцевому підсумку застосував подібне рішення
Джеймс Дженкінс

1

Як і у випадку з 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

З найкращими побажаннями!


Ідеально! Це те, що я шукав. Особлива подяка!
Phantom

0

Це для мене працює, в моїй країні субота та неділя - неробочі дні.

Для мене важливий час @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

0

Створіть таку функцію, як:

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

Ви можете викликати функцію, наприклад:

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

Або як:

select dbo.fn_WorkDays(StartDate, EndDate) 
from table1

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

Кінець


0

Нижче я знайшов 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

Один із підходів полягає в тому, щоб "пройти дати" від початку до кінця разом із виразом справи, який перевіряє, чи день не субота чи неділя, і позначив його (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

Я запозичив деякі ідеї у інших, щоб створити своє рішення. Я використовую вбудований код, щоб ігнорувати вихідні та федеральні свята США. У моєму середовищі EndDate може бути нульовим, але він ніколи не передуватиме 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.