İki tarih arasındaki iş günlerini sayın


163

SQL Server'da iki tarih arasındaki iş günü sayısını nasıl hesaplayabilirim?

Pazartesiden cumaya ve T-SQL olmalı.


5
İş günlerini tanımlayabilir misiniz? pazartesiden cumaya kadar? Büyük tatiller hariç mi? Hangi ülke? SQL'de yapılmalı mı?
Dave K

Yanıtlar:


301

Pazartesiden cumaya iş günleri için, bunu tek bir SELECT ile yapabilirsiniz, şöyle:

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)

Tatilleri dahil etmek istiyorsanız, biraz çalışmak zorundasınız ...


3
Ben sadece bu kodun her zaman çalışmadığını fark ettim! Bunu denedim: SET @StartDate = '28 -mar-2011 'SET @EndDate = '29 -mar-2011' 2 gün olarak sayılan cevap
greektreat

16
@greektreat İyi çalışıyor. Sadece @StartDate ve @EndDate sayıma dahil edilir. Pazartesi ile Salı arasında 1 gün sayılmasını istiyorsanız, ilk DATEDIFF'ten sonra "+ 1" i kaldırmanız yeterlidir. Sonra Fri-> Cts = 0, Cum-> Paz = 0, Cum-> Pzt = 1 alırsınız.
Joe Daley

6
@JoeDaley takibi olarak. Başlangıç ​​tarihini sayımdan çıkarmak için DATEDIFF'den sonra + 1'i kaldırdığınızda, bunun CASE kısmını da ayarlamanız gerekir. Bunu kullanarak sona erdirdim: + (VERİ ADI OLDUĞUNUN DURUMU (dw, @StartDate) = '1 BAŞLANGIÇ 0 SONRA' Cumartesi ') - (VERİ ADI OLDUĞUNUN DURUMU (dw, @EndDate) =' Cumartesi 'SONRA 1 BAŞKA 0 SONA)
Sequenzia

7
Datename işlevi yerel ayara bağlıdır. Daha sağlam ama aynı zamanda daha belirsiz bir çözüm, son iki satırı şu şekilde değiştirmektir:-(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'nın yorumunu netleştirmek için, sadece Pazar gününü terk ederek vaka açıklamalarını tamamen +(CASE WHEN DATENAME(dw, @StartDate) = 'Saturday' THEN 1 ELSE 0 END) - (CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)
KALDIRACAKSIN

32

In Çalışma Günleri Hesaplama Eğer bu konuda iyi bir makale bulabilirsiniz, ama gördüğünüz gibi bu gelişmiş değildir.

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

Özel bir takvim kullanmanız gerekiyorsa, bazı denetimler ve bazı parametreler eklemeniz gerekebilir. Umarım iyi bir başlangıç ​​noktası sağlar.


Bunun nasıl çalıştığını anlamak için bağlantıyı eklediğiniz için teşekkür ederiz. Sqlservercentral üzerine yazma harikaydı!
Chris Porter

20

Bogdan Maxim ve Peter Mortensen'e Tüm Krediler. Bu yazı, Ben sadece fonksiyon tatil ekledi (Bu datetime alan "HolDate" ile bir tablo "tblHolidays" varsayalım.

--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
Merhaba Dan B.Sadece sürümünüzün tblHolidays tablosunun Cumartesi ve Pazartesi günleri içermediğini varsadığını bildirmek için, bazen olur. Her neyse, sürümünüzü paylaştığınız için teşekkürler. Şerefe
Julio Nobre

3
Julio - Evet - Sürümüm Cumartesi ve Pazar günlerinin (Pazartesi günleri değil) hafta sonları olduğunu ve bu nedenle "iş dışı" gün olduğunu varsayıyor. Ama hafta sonları çalışıyorsanız, o zaman her gün bir "iş günü" sanırım ve cümlenin Cumartesi ve Pazar bölümünü yorumlayabilir ve sadece tblHolidays tablosuna tüm tatillerinizi ekleyebilirsiniz.
Dan B

1
Teşekkürler Dan. Ben tabloda işlevini alarak tüm tarihleri, tatil vb içerir benim DateDimensions olarak hafta sonları için bir çek ekleyerek, benim fonksiyonu olarak kullanmıştır, sadece ekledi: ve IsWeekend = 0 sonra burada [HolDate] StartDate ve bitiştarihi) arasında
AlsoKnownAsJazz

Tatil tablosunda hafta sonları tatil günleri varsa, şu ölçütleri değiştirebilirsiniz: WHERE HolDate BETWEEN @StartDate AND @EndDate AND DATEPART(dw, HolDate) BETWEEN 2 AND 6yalnızca Pazartesi'den Cuma'ya tatilleri saymak için.
Andre

7

Çalışma günlerini hesaplamak için başka bir yaklaşım, temel olarak bir tarih aralığında yinelenen ve Pazartesi - Cuma günleri bulunduğunda gün sayısı 1 olan bir WHILE döngüsü kullanmaktır. WHILE döngüsünü kullanarak iş günlerini hesaplamak için komut dosyasının tamamı aşağıda gösterilmiştir:

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 döngü seçeneği daha temiz ve daha az kod satırı kullansa da, özellikle tarih aralığınız birkaç yıla yayıldığında ortamınızda bir performans darboğazı olma potansiyeline sahiptir.

Bu makalede iş günlerini ve saatlerini nasıl hesaplayacağınıza ilişkin daha fazla yöntem görebilirsiniz: https://www.sqlshack.com/how-to-calculate-work-days-and-hours-in-sql-server/


6

Benim sürüm kullanarak bir işlevi olarak kabul edilen cevap DATEPART, bu yüzden ile bir dize karşılaştırma yapmak zorunda değilsiniz

DATENAME(dw, @StartDate) = 'Sunday'

Her neyse, benim iş tarihliiff fonksiyonu

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

Kod, XML veya veri örnekleri gönderiyorsanız, lütfen metin düzenleyicide bu satırları vurgulayın ve güzel biçimlendirmek ve sözdizimi vurgulamak için düzenleyici araç çubuğundaki "kod örnekleri" düğmesini ({}) tıklayın!
marc_s

Harika, çevre işlevlerine veya bunu kullanarak veritabanında güncellemeye gerek yok. Teşekkürler. Love the saltire btw :-)
Brian Scott

Süper çözüm. Bir webi evreninde 2 tablo sütunundaki tarihler arasındaki hafta içi günleri (MF) hesaplamak için kullanılan değişkenlerin formüllerini alt kümelere ekledim ... ((((DATEDIFF (gün, tablo.col1, tablo.col2) +1) - ((VAKA VERİ ADI (hafta içi, tablo.col2) 'Cumartesi' SONRA 1 NE ZAMAN 'Pazar' SONRA 2 BAŞKA 0 SON)) / 7) * 5) + (((TARİHLİFIF (gün, tablo.col1, tablo.col2 ) +1) - ((VAKA VERİ ADI (hafta içi, tablo.col2) 'Cumartesi' SONRA 1 SONRA 'Pazar' SONRA 2 BAŞKA 0 SON)))% 7)
Hilary

5

(Ayrıcalık hakkında yorum yapmaktan utanıyorum)

CMS'nin zarif çözümünde +1 gününden vazgeçmeye karar verirseniz , başlangıç ​​tarihiniz ve bitiş tarihiniz aynı hafta sonu ise olumsuz bir yanıt alırsınız. Yani, 2008/10/26 - 2008/10/26 -1 döndürür.

benim oldukça basit çözümüm:

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

.. ayrıca bitiş tarihinden sonraki başlangıç ​​tarihine sahip tüm hatalı yayınları sıfıra ayarlar . Aradığınız veya aramayacağınız bir şey.


5

Tatiller dahil tarihler arasındaki fark için şu şekilde gittim:

1) Tatiller ile Tablo:

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

2) Planlarım Tablo böyle vardı ve boş olan Work_Days sütununu doldurmak istedim:

    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) Yani "Work_Days" daha sonra benim sütun doldurmak için sadece yapmak zorunda kaldı:

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

Umarım yardımcı olabilirim.

Şerefe


1
Tatil çıkarmaları ile ilgili. Başlangıç ​​tarihi 1 Ocak ve bitiş tarihi 31 Aralık ise ne olur? Sadece 2 çıkarırsınız - bu yanlıştır. End_Date için tüm 'SELECT COUNT (*) FROM Holiday ...' yerine DATEDIFF (gün, Start_Date, Tarih) ve aynısını kullanmayı öneriyorum.
Illia Ratkevych

4

İşte iyi çalışan bir sürüm (sanırım). Tatil tablosu, şirketinizin gözlemlediği tatilleri içeren Holiday_date sütunlarını içerir.

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 

Bu tatil tarihleri ​​hafta sonları da düşebilir. Ve bazıları için, Pazar günü tatilin yerini bir sonraki Pazartesi alacak.
Irawan Soetomo

3

Bunun eski bir soru olduğunu biliyorum, ancak birkaç öğe olduğundan ve doğru şekilde birikmek için günlere ihtiyacım olduğundan başlangıç ​​günleri hariç iş günleri için bir formüle ihtiyacım vardı.

Yinelemesiz cevapların hiçbiri benim için işe yaramadı.

Gibi bir tanım kullandım

Gece yarısından pazartesiye, salı, çarşamba, perşembe ve cuma günü geçme sayısı

(diğerleri pazartesi yerine gece yarısından cumartesiye sayabilir)

Bu formülü buldum

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
Bunu benim için yaptı ama küçük bir değişiklik yapmak zorunda kaldım. @StartDateCumartesi ya da cuma ne zaman olduğunu açıklamıyordu. İşte benim sürüm: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, cumartesi-pazar 0 döndürür, cumartesi-pazartesi 1 döndürür, cuma - cumartesi 0 döndürür. Hepsi tanımımla tutarlı. Kodunuz doğru şekilde birikmeyecek (örneğin,
cumadan

3

Bu temel olarak CMS'nin belirli bir dil ayarına güvenmeden cevabıdır. Jenerik için çekim yaptığımız için bu, tüm @@datefirstayarlar için de çalışması gerektiği anlamına gelir .

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, ...)her zaman haftalar boyunca Cumartesi-Pazar sınırı kullanır, böylece ifade deterministiktir ve değiştirilmesine gerek yoktur (hafta içi tanımımız tutarlı bir şekilde Pazartesi'den Cuma'ya kadar olduğu sürece). Gün numaralandırma @@datefirstayara ve değiştirilmiş hesaplamalar bu düzeltmeyi bazı modüler aritmetiğin küçük komplikasyonu ile halleder.

Cumartesi / Pazar gününü ele almanın daha temiz bir yolu, haftanın bir gün değerini çıkarmadan önceki tarihleri ​​çevirmektir. Vites değiştirdikten sonra, değerler Pazar günü 1 ile başlayan ve Cumartesi günü 7 ile biten sabit (ve muhtemelen daha tanıdık) bir numaralandırma ile aynı doğrultuda olacaktır.

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

Çözümün bu biçimini en azından 2002'ye kadar ve bir Itzik Ben-Gan makalesine kadar izledim. ( https://technet.microsoft.com/en-us/library/aa175781(v=sql.80).aspx ) Daha yeni datetürler tarih aritmetiğine izin vermediği için küçük bir değişiklik yapmasına rağmen, aksi takdirde aynıdır.

DÜZENLEME: Bir +1şekilde bırakılmış olanı geri ekledim . Ayrıca, bu yöntemin her zaman başlangıç ​​ve bitiş günlerini saydığını belirtmek gerekir. Ayrıca bitiş tarihinin başlangıç ​​tarihinden sonra veya sonra olduğunu varsayar.


Bunun, hafta sonları birçok tarih için yanlış sonuçlar döndüreceğini unutmayın, böylece upp eklemezler (Cum-> Pzt Cum-> Cts + Cts-> Paz + Paz-> Pzt ile aynı olmalıdır). Cum-> Cts 0 (doğru), Cts-> Güneş 0 (yanlış -1), Paz-> Pzt 1 (yanlış 0) olmalıdır. Bundan sonraki diğer hatalar Sat-> Cmt = -1, Pzt-> Paz = -1,
Pzt-

@adrianm Sorunları giderdiğime inanıyorum. Aslında sorun her zaman birer birer kapalı olmasıydı çünkü bir şekilde kazara bu parçayı düşürdüm.
shawnt00

Güncelleme için teşekkürler. Formülünüzün başlangıç ​​tarihini hariç tuttuğunu düşündüm. Kendim çözdüm ve başka bir cevap olarak ekledim.
adrianm

2

Bir tarih tablosu kullanma:

    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;

Buna sahip değilseniz, bir sayı tablosu kullanabilirsiniz:

    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

İkisi de hızlı olmalı ve belirsizliği / karmaşıklığı ortadan kaldırıyor. İlk seçenek en iyisidir, ancak bir takvim tablonuz yoksa her zaman bir CTE ile bir numara tablosu oluşturabilirsiniz.


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

Eğer bir işlevi kullanmak için gidiyoruz, gibi bir tablo tabanlı fonksiyonu ile gitmek daha iyi olabilir Mário Meyrelles tarafından cevap
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

Burada çeşitli örnekler aldım, ancak özel durumumda teslimat için bir @PromisedDate ve öğenin gerçek alımı için bir @ReceivedDate var. Bir öğeyi "PromisedDate" den önce alındığında, işleve aktarılan tarihleri ​​takvim sırasına göre sipariş etmediğim sürece hesaplamalar doğru bir şekilde toplanmıyordu. Her seferinde tarihleri ​​kontrol etmek istemiyorum, bunu benim için halletme işlevini değiştirdim.

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

Belirli bir tarihe iş günü eklemeniz gerekiyorsa, aşağıda açıklanan takvim tablosuna bağlı bir işlev oluşturabilirsiniz:

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

DATEDIFF ile olduğu gibi, bitiş tarihini aralığın bir parçası olarak görmüyorum. @StartDate ile @EndDate arasındaki Pazar günlerinin sayısı, bir "başlangıç" Pazartesi ile @EndDate arasındaki Pazar günlerinin sayısı ile bu "başlangıç" Pazartesi ile @StartDate arasındaki Pazar günlerinin sayısıdır. Bunu bilerek, iş günü sayısını aşağıdaki gibi hesaplayabiliriz:

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

Saygılarımla!


Mükemmel! Aradığım şey buydu. Özel teşekkür!
Phantom

0

Bu benim için çalışıyor, ülkemde cumartesi ve pazar günleri iş günü değil.

Benim için @StartDate ve @EndDate zamanı önemli.

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

Şunun gibi bir işlev oluşturun:

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

İşlevi aşağıdaki gibi çağırabilirsiniz:

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

Veya beğen:

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

Son


0

Aşağıdaki TSQL oldukça zarif bir çözüm buldum (işlevleri çalıştırmak için izinleri yok). DATEDIFFYoksayları buldum ve DATEFIRSThaftanın ilk gününün Pazartesi olmasını istedim. Ayrıca ilk iş gününün sıfır olarak ayarlanmasını istedim ve eğer hafta sonu hafta sonu düşerse sıfır olur. Bu, biraz farklı bir gereksinimi olan birine yardımcı olabilir :)

Banka tatillerini işlemez

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

Bir yaklaşım, günün Cumartesi veya Pazar olup olmadığını kontrol eden ve işaretleyen bir vaka ifadesiyle birlikte başından sonuna kadar 'tarihler' yürümektir (hafta içi 1, hafta sonu için 0). Ve sonunda, hafta günlerinin sayısını vermek için sadece bayrakların toplamı (diğer bayrak 0 olduğu için 1 bayrak sayısına eşit olacaktır).

Başlangıç ​​tarihinden bitiş tarihine 'döngü' için bir dizi sayı üreten bir GetNums (startNumber, endNumber) yardımcı program işlevi kullanabilirsiniz. Bir uygulama için http://tsql.solidq.com/SourceCodes/GetNums.txt adresine bakın . Mantık, tatile hitap etmek için de genişletilebilir (örneğin, bir tatil masanız varsa)

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

Çözümümü oluşturmak için başkalarından bazı fikirler ödünç aldım. Haftasonlarını ve ABD federal tatillerini görmezden gelmek için satır içi kodu kullanıyorum. Benim çevremde, EndDate boş olabilir, ancak hiçbir zaman StartDate'den önce olmayacak.

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.