นับวันทำงานระหว่างสองวัน


163

ฉันจะคำนวณจำนวนวันทำงานระหว่างสองวันใน SQL Server ได้อย่างไร

วันจันทร์ถึงวันศุกร์และต้องเป็น T-SQL


5
คุณสามารถกำหนดวันทำงานได้ไหม วันจันทร์ถึงวันศุกร์? ไม่รวมวันหยุดสำคัญ ประเทศอะไร? ต้องทำใน SQL หรือไม่
Dave K

คำตอบ:


301

สำหรับวันทำงานวันจันทร์ถึงวันศุกร์คุณสามารถเลือกได้ด้วยตัวเลือกเดียวดังนี้:

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 แรก จากนั้นคุณจะได้รับ Fri-> Sat = 0, Fri-> Sun = 0, Fri-> Mon = 1
Joe Daley

6
ในฐานะผู้ติดตาม @JoeDaley เมื่อคุณลบ + 1 หลังจาก DATEDIFF เพื่อแยก startdate ออกจากการนับคุณจะต้องปรับส่วน CASE ของสิ่งนี้ ฉันสิ้นสุดการใช้นี้: + (กรณีที่ DATENAME (DW, @StartDate) = 'วันเสาร์แล้ว 1 ELSE 0 END) - (กรณีที่ DATENAME (DW, @EndDate) =' วันเสาร์แล้ว 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)
Andy Raddatz

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 ยอดเยี่ยมมาก!
Chris Porter

20

เครดิตทั้งหมดให้กับ Bogdan Maxim & Peter Mortensen นี่คือโพสต์ของพวกเขาฉันเพิ่งเพิ่มวันหยุดไปยังฟังก์ชัน (สมมติว่าคุณมีตาราง "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 ไม่มีวันเสาร์และวันจันทร์ซึ่งบางครั้งก็เกิดขึ้น อย่างไรก็ตามขอขอบคุณที่แบ่งปันเวอร์ชันของคุณ ไชโย
Julio Nobre

3
Julio - ใช่ - รุ่นของฉันคิดว่าวันเสาร์และวันอาทิตย์ (ไม่ใช่วันจันทร์) เป็นวันหยุดสุดสัปดาห์และไม่ใช่วันที่ไม่ใช่ธุรกิจ แต่ถ้าคุณทำงานวันหยุดสุดสัปดาห์ฉันคิดว่าทุกวันเป็น "วันทำงาน" และคุณสามารถแสดงความคิดเห็นในส่วนของวันเสาร์และวันอาทิตย์และเพิ่มวันหยุดของคุณทั้งหมดลงในตาราง tblHolidays
Dan B

1
ขอบคุณแดน ฉันนิติบุคคลที่จัดตั้งขึ้นนี้ในการทำงานของฉันเพิ่มการตรวจสอบสำหรับวันหยุดสุดสัปดาห์เป็น DateDimensions ของฉันรวมถึงโต๊ะทุกวันวันหยุด ฯลฯ การทำงานของคุณผมเพิ่งเพิ่มและ IsWeekend = 0 หลังจากที่ [HolDate] ระหว่าง StartDate และ EndDate)
AlsoKnownAsJazz

หากตารางวันหยุดมีวันหยุดในวันหยุดสุดสัปดาห์คุณสามารถแก้ไขเกณฑ์เช่นนี้: WHERE HolDate BETWEEN @StartDate AND @EndDate AND DATEPART(dw, HolDate) BETWEEN 2 AND 6นับเฉพาะวันหยุดตั้งแต่วันจันทร์ถึงวันศุกร์
Andre

7

อีกวิธีหนึ่งในการคำนวณวันทำงานคือการใช้วง WHILE ซึ่งจะวนซ้ำตามช่วงวันที่และเพิ่มขึ้นทีละ 1 ทุกครั้งที่พบว่าเป็นวันจันทร์ - ศุกร์ สคริปต์ที่สมบูรณ์สำหรับการคำนวณวันทำการโดยใช้ลูปในขณะที่แสดงด้านล่าง:

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/ https://www.sqlshack.com/


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

เยี่ยมมากไม่จำเป็นต้องใช้ฟังก์ชั่นอุปกรณ์ต่อพ่วงหรืออัพเดทฐานข้อมูลโดยใช้สิ่งนี้ ขอบคุณ รัก birew btw :-)
Brian Scott

สุดยอดโซลูชั่น ฉัน subbed ในสูตรสำหรับตัวแปรที่จะใช้ใน webi Universe เพื่อคำนวณวันทำงาน (MF) ระหว่างวันที่ในคอลัมน์ตาราง 2 เช่น ... (((((DATEDIFF (วัน, table.col1, table.col2) +1)) - ((กรณีชื่อ (วันทำงาน, table.col2) เมื่อ 'วันเสาร์' แล้ว 1 เมื่อ 'วันอาทิตย์' จากนั้นอีก 2 สิ้นสุด 0)) / 7) * 5) + ((DATEDIFF (วัน, table.col1, table.col2 ) +1) - ((ชื่อกรณี (วันทำงาน, table.col2) เมื่อ 'วันเสาร์' จากนั้น 1 เมื่อ 'วันอาทิตย์' จากนั้น 2 อีกครั้ง 0 จบ)))% 7)
Hilary

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, Date) และเหมือนกันสำหรับ End_Date แทนที่จะเป็น 'SELECT COUNT (*) จากวันหยุด ... '
Illia Ratkevych

4

นี่คือรุ่นที่ใช้งานได้ดี (ฉันคิดว่า) ตารางวันหยุดมีคอลัมน์วันหยุดที่มีวันหยุดที่ บริษัท ของคุณสังเกต

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 และบทความ Itzik Ben-Gan ( https://technet.microsoft.com/en-us/library/aa175781(v=sql.80).aspx ) แม้ว่าจะต้องใช้การปรับแต่งเล็กน้อยตั้งแต่ใหม่dateประเภทที่นั้นไม่อนุญาตให้ใช้การคำนวณวันที่

แก้ไข: ฉันเพิ่มกลับ+1ที่ถูกทิ้งไว้อย่างใด เป็นที่น่าสังเกตว่าวิธีนี้จะนับวันเริ่มต้นและวันที่สิ้นสุดเสมอ นอกจากนี้ยังอนุมานว่าวันที่สิ้นสุดอยู่ในหรือหลังวันที่เริ่มต้น


โปรดทราบว่าสิ่งนี้จะส่งคืนผลลัพธ์ที่ผิดสำหรับหลาย ๆ วันในวันหยุดสุดสัปดาห์ดังนั้นพวกเขาจึงไม่เพิ่ม upp (ศุกร์ -> จันทร์ควรเหมือนกับศุกร์ -> เสาร์ + เสาร์ -> อาทิตย์ + อาทิตย์ -> จันทร์) วันศุกร์ -> วันเสาร์ควรเป็น 0 (ถูกต้อง) วันเสาร์ -> ดวงอาทิตย์ควรเป็น 0 (ผิด -1), อาทิตย์ -> วันจันทร์ควรเป็น 1 (ผิด 0) ข้อผิดพลาดอื่น ๆ ต่อจากนี้คือ Sat-> Sat = -1, Sun-> Sun = -1, Sun-> Sat = 4
adrianm

@adrianm ฉันเชื่อว่าฉันได้แก้ไขปัญหาแล้ว ที่จริงปัญหาคือว่ามันถูกปิดโดยหนึ่งเพราะฉันได้ลดลงส่วนหนึ่งโดยบังเอิญ
shawnt00

ขอบคุณสำหรับการอัพเดท. ฉันคิดว่าสูตรของคุณไม่รวมวันที่เริ่มต้นซึ่งเป็นสิ่งที่ฉันต้องการ แก้ไขด้วยตนเองและเพิ่มเป็นคำตอบอื่น
adrianm

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

หากคุณกำลังจะใช้ฟังก์ชั่นมันอาจจะดีกว่าถ้าใช้ฟังก์ชั่นบนโต๊ะตามคำตอบของ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

1

ฉันนำตัวอย่างต่าง ๆ มาที่นี่ แต่ในสถานการณ์เฉพาะของฉันเรามี @PromisedDate สำหรับการจัดส่งและ @RivedivedDate สำหรับการรับสินค้าจริง เมื่อได้รับรายการก่อน "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 ฉันลงเอยด้วยการใช้วิธีแก้ปัญหาแบบเดียวกันนี้
James Jenkins

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.