Właśnie zdałem sobie sprawę, że ten kod nie zawsze działa! próbowałem tego: SET @StartDate = '28 -mar-2011 'SET @EndDate = '29 -mar-2011' odpowiedź liczyła to jako 2 dni
greektreat
16
@greektreat Działa dobrze. Po prostu zarówno @StartDate, jak i @EndDate są uwzględnione w liczbie. Jeśli chcesz, aby od poniedziałku do wtorku liczył się jako 1 dzień, po prostu usuń „+ 1” po pierwszym DATEDIFF. Wtedy otrzymasz również piątek -> sob = 0, piątek -> nie = 0, piątek -> poniedziałek = 1.
Joe Daley
6
Jako kontynuacja @JoeDaley. Kiedy usuniesz + 1 po DATEDIFF, aby wykluczyć datę początkową z zliczania, musisz również dostosować część CASE. Skończyło się na tym: + (CASE WHEN DATENAME (dw, @StartDate) = 'Saturday' THEN 1 ELSE 0 END) - (CASE WHEN DATENAME (dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)
Sequenzia
7
Funkcja datename jest zależna od ustawień regionalnych. Bardziej solidnym, ale też mniej zrozumiałym rozwiązaniem jest zastąpienie dwóch ostatnich wierszy przez:-(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
Aby wyjaśnić komentarz @ Sequenzia, należy całkowicie USUNĄĆ opisy przypadków dotyczące niedzieli, pozostawiając tylko+(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
W rozdziale Obliczanie dni pracy można znaleźć dobry artykuł na ten temat, ale jak widać nie jest on aż tak zaawansowany.
--Changing current database to the Master database allows function to be shared by everyone.USE MASTER
GO
--If the function already exists, drop it.IFEXISTS(SELECT*FROM dbo.SYSOBJECTS
WHERE ID = OBJECT_ID(N'[dbo].[fn_WorkDays]')AND XType IN(N'FN', N'IF', N'TF'))DROPFUNCTION[dbo].[fn_WorkDays]
GO
CREATEFUNCTION 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 ISNULLRETURNNULL--If the End Date is null, populate with Start Date value so will have two dates (required by DATEDIFF below).IF@EndDate ISNULLSELECT@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-(CASEWHEN DATENAME(dw,@StartDate)='Sunday'THEN1ELSE0END)--If EndDate is a Saturday, Subtract 1-(CASEWHEN DATENAME(dw,@EndDate)='Saturday'THEN1ELSE0END))END
GO
Jeśli chcesz użyć kalendarza niestandardowego, może być konieczne dodanie kontroli i parametrów. Miejmy nadzieję, że będzie to dobry punkt wyjścia.
Dziękujemy za dołączenie linku do zrozumienia, jak to działa. Pisanie na sqlservercentral było świetne!
Chris Porter
20
Wszystkie podziękowania dla Bogdana Maxima i Petera Mortensena. To jest ich post, właśnie dodałem święta do funkcji (przy założeniu, że masz tabelę „tblHolidays” z polem daty i godziny „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.IFEXISTS(SELECT*FROM dbo.SYSOBJECTS
WHERE ID = OBJECT_ID(N'[dbo].[fn_WorkDays]')AND XType IN(N'FN', N'IF', N'TF'))DROPFUNCTION[dbo].[fn_WorkDays]
GO
CREATEFUNCTION 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 ISNULLRETURNNULL--If the End Date is null, populate with Start Date value so will have two dates (required by DATEDIFF below).IF@EndDate ISNULLSELECT@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-(CASEWHEN DATENAME(dw,@StartDate)='Sunday'THEN1ELSE0END)--If EndDate is a Saturday, Subtract 1-(CASEWHEN DATENAME(dw,@EndDate)='Saturday'THEN1ELSE0END)--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)
*/
Cześć Dan B. Chcę tylko poinformować, że Twoja wersja zakłada, że tabela tblHolidays nie zawiera sobót i poniedziałków, co czasami się zdarza. W każdym razie, dziękuję za udostępnienie swojej wersji. Pozdrawiam
Julio Nobre
3
Julio - Tak - moja wersja zakłada, że soboty i niedziele (nie poniedziałek) są weekendami, a zatem nie są dniem „wolnym od pracy”. Ale jeśli pracujesz w weekendy, myślę, że każdy dzień jest „dniem roboczym” i możesz skomentować sobotę i niedzielę części klauzuli i po prostu dodać wszystkie swoje święta do tabeli tblHolidays.
Dan B,
1
Dzięki Dan. Włączyłem to do mojej funkcji, dodając czek na weekendy, ponieważ moja tabela DateDimensions zawiera wszystkie daty, święta itp. Biorąc twoją funkcję, właśnie dodałem: i IsWeekend = 0 po gdzie [HolDate] między StartDate a EndDate)
AlsoKnownAsJazz
Jeśli tabela Święta zawiera dni wolne w weekendy, możesz zmienić kryteria w następujący sposób: WHERE HolDate BETWEEN @StartDate AND @EndDate AND DATEPART(dw, HolDate) BETWEEN 2 AND 6aby liczone były tylko dni wolne od poniedziałku do piątku.
Andre
7
Innym podejściem do obliczania dni roboczych jest użycie pętli WHILE, która w zasadzie iteruje zakres dat i zwiększa go o 1 za każdym razem, gdy znajdzie się dzień przypadający na poniedziałek - piątek. Pełny skrypt do obliczania dni roboczych za pomocą pętli WHILE przedstawiono poniżej:
CREATEFUNCTION[dbo].[fn_GetTotalWorkingDaysUsingLoop](@DateFrom DATE,@DateTo DATE
)
RETURNS INT
ASBEGINDECLARE@TotWorkingDays INT=0;WHILE@DateFrom <=@DateTo
BEGINIF DATENAME(WEEKDAY,@DateFrom)IN('Monday','Tuesday','Wednesday','Thursday','Friday')BEGINSET@TotWorkingDays =@TotWorkingDays +1;END;SET@DateFrom = DATEADD(DAY,1,@DateFrom);END;RETURN@TotWorkingDays;END;
GO
Chociaż opcja pętli WHILE jest czystsza i wykorzystuje mniej wierszy kodu, może stanowić wąskie gardło wydajności w Twoim środowisku, szczególnie gdy zakres dat obejmuje kilka lat.
Moja wersja zaakceptowanej odpowiedzi jako funkcji używającej DATEPART, więc nie muszę robić porównania ciągów w linii z
DATENAME(dw,@StartDate)='Sunday'
W każdym razie, oto moja funkcja z datą biznesową
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATEFUNCTION BDATEDIFF
(@startdate as DATETIME,@enddate as DATETIME
)
RETURNS INT
ASBEGINDECLARE@res int
SET@res =(DATEDIFF(dd,@startdate,@enddate)+1)-(DATEDIFF(wk,@startdate,@enddate)*2)-(CASEWHEN DATEPART(dw,@startdate)=1THEN1ELSE0END)-(CASEWHEN DATEPART(dw,@enddate)=7THEN1ELSE0END)RETURN@res
END
GO
DECLARE@TotalDays INT,@WorkDays INT
DECLARE@ReducedDayswithEndDate INT
DECLARE@WeekPart INT
DECLARE@DatePart INT
SET@TotalDays= DATEDIFF(day,@StartDate,@EndDate)+1SELECT@ReducedDayswithEndDate =CASE DATENAME(weekday,@EndDate)WHEN'Saturday'THEN1WHEN'Sunday'THEN2ELSE0ENDSET@TotalDays=@TotalDays-@ReducedDayswithEndDate
SET@WeekPart=@TotalDays/7;SET@DatePart=@TotalDays%7;SET@WorkDays=(@WeekPart*5)+@DatePart
RETURN@WorkDays
Jeśli kod pocztowy, próbki XML lub danych, należy zaznaczyć te linie w edytorze tekstowym i kliknij na przycisk ({}) na pasku narzędzi edytora „Kod próbki”, aby ładnie format i składnia go podświetlić!
marc_s
Świetnie, nie ma potrzeby korzystania z funkcji peryferyjnych lub aktualizacji bazy danych. Dzięki. Przy okazji uwielbiam saltire :-)
Brian Scott,
Super rozwiązanie. W formułach dla zmiennych, które mają być używane we Wszechświecie webi, w celu obliczenia dni tygodnia (MF) między datami w 2 kolumnach tabeli, tak jak to ... ((((DATEDIFF (day, table.col1, table.col2) +1) - ((CASE DATENAME (dzień tygodnia, tabela.col2) GDY 'Sobota' TO 1 GDY 'Niedziela' TO 2 INNE 0 KONIEC))) / 7) * 5) + (((DATEDIFF (dzień, tabela.col1, tabela.col2) ) +1) - ((CASE DATENAME (dzień powszedni, table.col2) GDY „Sobota” NASTĘPNIE 1 GDY „Niedziela” WTEDY 2 INNY 0 KONIEC)))% 7)
Hilary
5
(O kilka punktów brakuje mi uprawnień do komentowania)
Jeśli zdecydujesz się zrezygnować z +1 dnia w eleganckim rozwiązaniu CMS , pamiętaj, że jeśli data rozpoczęcia i data zakończenia przypadają na ten sam weekend, otrzymasz odpowiedź negatywną. Tj., 2008/10/26 do 2008/10/26 zwraca -1.
Jeśli chodzi o odejmowanie wakacji. Co się stanie, jeśli datą rozpoczęcia jest 1 stycznia, a datą zakończenia 31 grudnia? Odejmiesz tylko 2 - co jest złe. Proponuję użyć DATEDIFF (dzień, Data_początkowa, Data) i tego samego dla Data_końcowa zamiast całego „WYBIERZ LICZNIK (*) OD DNI ...”.
Illia Ratkevych
4
Oto wersja, która działa dobrze (tak mi się wydaje). Tabela dni wolnych zawiera kolumny Data_urlopu, które zawierają święta obchodzone w firmie.
Te dni świąteczne mogą również przypadać w weekendy. A dla niektórych niedzielne wakacje zostaną zastąpione przez następny poniedziałek.
Irawan Soetomo
3
Wiem, że to stare pytanie, ale potrzebowałem wzoru na dni robocze bez daty początkowej, ponieważ mam kilka pozycji i potrzebuję dni, aby poprawnie się zakumulować.
Żadna z nie-iteracyjnych odpowiedzi nie działała dla mnie.
Użyłem definicji podobnej
Ile razy mija północ do poniedziałku, wtorku, środy, czwartku i piątku
(inni mogą liczyć północ do soboty zamiast poniedziałku)
Ten zrobił to dla mnie, ale musiałem zrobić małą zmianę. Nie uwzględniono, kiedy @StartDatejest sobota czy piątek. Oto moja wersja: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, od soboty do niedzieli zwraca 0, od soboty do poniedziałku zwraca 1, od piątku do soboty zwraca 0. Wszystkie są zgodne z moją definicją. Twój kod nie gromadzi się poprawnie (np.
Zwraca
3
To jest w zasadzie odpowiedź CMS bez polegania na konkretnym ustawieniu językowym. A ponieważ robimy zdjęcia generyczne, oznacza to, że powinno działać również dla wszystkich @@datefirstustawień.
datediff(day,<start>,<end>)+1- datediff(week,<start>,<end>)*2/* if start is a Sunday, adjust by -1 */+casewhen datepart(weekday,<start>)=8-@@datefirst then-1else0end/* if end is a Saturday, adjust by -1 */+casewhen datepart(weekday,<end>)=(13-@@datefirst)%7+1then-1else0end
datediff(week, ...) zawsze używa granicy od soboty do niedzieli przez tygodnie, więc wyrażenie jest deterministyczne i nie trzeba go modyfikować (o ile nasza definicja dni tygodnia jest konsekwentnie od poniedziałku do piątku). Numeracja dni różni się w zależności od @@datefirst ustawienia i zmodyfikowane obliczenia obsługują tę korektę z niewielką komplikacją związaną z arytmetyką modularną.
Czystszym sposobem radzenia sobie z sobotą / niedzielą jest przetłumaczenie dat przed wyodrębnieniem wartości dnia tygodnia. Po przesunięciu wartości wrócą do stałej (i prawdopodobnie bardziej znanej) numeracji, która zaczyna się od 1 w niedzielę i kończy na 7 w sobotę.
Śledziłem tę formę rozwiązania przynajmniej do 2002 roku i artykułu Itzika Ben-Gana. ( https://technet.microsoft.com/en-us/library/aa175781(v=sql.80).aspx ) Chociaż wymagało to niewielkiej zmiany, ponieważ nowsze datetypy nie pozwalają na arytmetykę dat, poza tym jest identyczny.
EDYCJA: dodałem z powrotem to +1, co zostało w jakiś sposób pominięte. Warto również zauważyć, że ta metoda zawsze liczy dzień rozpoczęcia i zakończenia. Zakłada się również, że data zakończenia przypada w dniu rozpoczęcia lub po niej.
Zwróć uwagę, że to zwróci błędne wyniki dla wielu dat w weekendy, więc nie sumują się one (piątek> poniedziałek powinien być taki sam jak piątek -> sobota + sobota -> niedziela + niedziela -> poniedziałek). Piątek -> sobota powinna wynosić 0 (poprawnie), sobota-> niedziela powinna wynosić 0 (źle -1), niedziela-> poniedziałek powinna wynosić 1 (źle 0). Inne błędy wynikające z tego to Sat-> Sat = -1, Sun-> Sun = -1, Sun-> Sat = 4
adrianm
@adrianm Myślę, że naprawiłem problemy. Właściwie problem polegał na tym, że zawsze był wyłączony o jeden, ponieważ w jakiś sposób przypadkowo upuściłem tę część.
shawnt00
Dziękuję za aktualizację. Myślałem, że twoja formuła wyklucza datę rozpoczęcia, czego potrzebowałem. Rozwiązałem to samodzielnie i dodałem jako kolejną odpowiedź.
adrianm
2
Korzystanie z tabeli dat:
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;
Jeśli tego nie masz, możesz użyć tabeli liczb:
DECLARE@StartDate datetime ='2014-01-01',@EndDate datetime ='2014-01-31';SELECT
SUM(CASEWHEN DATEPART(dw, DATEADD(dd, Number-1,@StartDate))BETWEEN2AND6THEN1ELSE0END)As NumberOfWeekDays
FROM dbo.Numbers
WHERE Number <= DATEDIFF(dd,@StartDate,@EndDate)+1-- Number table starts at 1, we want a 0 base
Oba powinny być szybkie i eliminuje niejednoznaczność / złożoność. Pierwsza opcja jest najlepsza, ale jeśli nie masz tabeli kalendarza, zawsze możesz utworzyć tabelę liczb z CTE.
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)+1SELECT@ReducedDayswithEndDate =CASE DATENAME(weekday,@EndDate)WHEN'Saturday'THEN1WHEN'Sunday'THEN2ELSE0ENDSET@TotalDays=@TotalDays-@ReducedDayswithEndDate
SET@WeekPart=@TotalDays/7;SET@DatePart=@TotalDays%7;SET@WorkDays=(@WeekPart*5)+@DatePart
SELECT@WorkDays
Wziąłem tutaj różne przykłady, ale w mojej konkretnej sytuacji mamy @PromisedDate do dostawy i @ReceivedDate do faktycznego odbioru przedmiotu. Gdy element został odebrany przed „PromisedDate”, obliczenia nie były sumowane poprawnie, chyba że zamówiłem daty przekazane do funkcji według kolejności kalendarza. Nie chcąc za każdym razem sprawdzać dat, zmieniłem funkcję, żeby sobie z tym poradzić.
CreateFUNCTION[dbo].[fnGetBusinessDays](@PromiseDate date,@ReceivedDate date
)
RETURNS integer
ASBEGINDECLARE@days integer
SELECT@days =Casewhen@PromiseDate >@ReceivedDate Then
DATEDIFF(d,@PromiseDate,@ReceivedDate)+
ABS(DATEDIFF(wk,@PromiseDate,@ReceivedDate))*2+CASEWHEN DATENAME(dw,@PromiseDate)<>'Saturday'AND DATENAME(dw,@ReceivedDate)='Saturday'THEN1WHEN DATENAME(dw,@PromiseDate)='Saturday'AND DATENAME(dw,@ReceivedDate)<>'Saturday'THEN-1ELSE0END+(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-CASEWHEN DATENAME(dw,@PromiseDate)<>'Saturday'AND DATENAME(dw,@ReceivedDate)='Saturday'THEN1WHEN DATENAME(dw,@PromiseDate)='Saturday'AND DATENAME(dw,@ReceivedDate)<>'Saturday'THEN-1ELSE0END-(Select COUNT(*)FROM CompanyHolidays
WHERE HolidayDate BETWEEN@PromiseDate and@ReceivedDate
AND DATENAME(dw, HolidayDate)<>'Saturday'AND DATENAME(dw, HolidayDate)<>'Sunday')EndRETURN(@days)END
Jeśli potrzebujesz dodać dni robocze do podanej daty, możesz stworzyć funkcję zależną od tabeli kalendarza, opisaną poniżej:
CREATETABLE Calendar
(
dt SMALLDATETIME PRIMARYKEY,
IsWorkDay BIT
);--fill the rows with normal days, weekends and holidays.createfunction AddWorkingDays (@initialDate smalldatetime,@numberOfDays int)
returns smalldatetime asbegindeclare@result smalldatetime
set@result =(select t.dt from(select dt, ROW_NUMBER()over(orderby dt)as daysAhead from calendar
where dt >@initialDate
and IsWorkDay =1) t
where t.daysAhead =@numberOfDays
)return@result
end
Podobnie jak w przypadku DATEDIFF, nie uważam daty końcowej za część interwału. Liczba (na przykład) niedziel między @StartDate a @EndDate to liczba niedziel między „początkowym” poniedziałkiem a @EndDate minus liczba niedziel między tym „początkowym” poniedziałek a @StartDate. Wiedząc o tym, możemy obliczyć liczbę dni roboczych w następujący sposób:
CREATEFUNCTION dbo.fn_WorkDays(@StartDate DATETIME,@EndDate DATETIME=NULL)
RETURNS INT
ASBEGINDECLARE@Days int
SET@Days =0IF@EndDate =NULLSET@EndDate = EOMONTH(@StartDate)--last date of the monthWHILE DATEDIFF(dd,@StartDate,@EndDate)>=0BEGINIF DATENAME(dw,@StartDate)<>'Saturday'and DATENAME(dw,@StartDate)<>'Sunday'andNot((Day(@StartDate)=1And Month(@StartDate)=1))--New Year's Day.andNot((Day(@StartDate)=4And Month(@StartDate)=7))--Independence Day.BEGINSET@Days =@Days +1ENDSET@StartDate = DATEADD(dd,1,@StartDate)ENDRETURN@Days
END
Poniższy TSQL uznałem za dość eleganckie rozwiązanie (nie mam uprawnień do uruchamiania funkcji). Znalazłem DATEDIFFignorowania DATEFIRSTi chciałem, aby mój pierwszy dzień tygodnia był w poniedziałek. Chciałem też, aby pierwszy dzień roboczy był ustawiony na zero i jeśli wypadnie w weekend, poniedziałek będzie równy zero. Może to pomóc komuś, kto ma nieco inne wymagania :)
Nie obsługuje dni wolnych od pracy
SET DATEFIRST 1SELECT,(DATEDIFF(DD,[StartDate],[EndDate]))-(DATEDIFF(wk,[StartDate],[EndDate]))-(DATEDIFF(wk, DATEADD(dd,-@@DATEFIRST,[StartDate]), DATEADD(dd,-@@DATEFIRST,[EndDate])))AS[WorkingDays]FROM/*Your Table*/
Jednym ze sposobów jest „przechodzenie po datach” od początku do końca w połączeniu z wyrażeniem przypadku, które sprawdza, czy dzień nie jest sobotą lub niedzielą, i zaznacza go (1 dla dnia tygodnia, 0 dla weekendu). I na koniec wystarczy suma flag (byłaby równa liczbie 1 flag, ponieważ druga flaga to 0), aby uzyskać liczbę dni tygodnia.
Możesz użyć funkcji narzędziowej typu GetNums (startNumber, endNumber), która generuje serię liczb dla „zapętlenia” od daty początkowej do końcowej. Zobacz http://tsql.solidq.com/SourceCodes/GetNums.txt, aby zapoznać się z implementacją. Logikę można również rozszerzyć, aby obsłużyć święta (powiedzmy, jeśli masz świąteczny stół)
declare@date1 as datetime ='19900101'declare@date2 as datetime ='19900120'select sum(casewhen DATENAME(DW,currentDate)notin('Saturday','Sunday')then1else0end)as noOfWorkDays
from dbo.GetNums(0,DATEDIFF(day,@date1,@date2)-1)as Num
crossapply(select DATEADD(day,n,@date1))as Dates(currentDate)
Kilka pomysłów pożyczyłem od innych, aby stworzyć swoje rozwiązanie. Używam kodu wbudowanego, aby ignorować weekendy i święta federalne w USA. W moim środowisku EndDate może mieć wartość null, ale nigdy nie będzie poprzedzać StartDate.
CREATEFUNCTION dbo.ufn_CalculateBusinessDays(@StartDate DATE,@EndDate DATE =NULL)
RETURNS INT
ASBEGINDECLARE@TotalBusinessDays INT =0;DECLARE@TestDate DATE =@StartDate;IF@EndDate ISNULLRETURNNULL;WHILE@TestDate <@EndDate
BEGINDECLARE@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 holidaySELECT@TotalBusinessDays +=(SELECTCASE--Saturday OR SundayWHEN@DayOfWeek IN(6,7)THEN0--New Year's DayWHEN@Month =1AND@Day =1THEN0--MLK Jr. DayWHEN@Month =1AND@DayOfWeek =1AND@DayOccurrence =3THEN0--G. Washington's BirthdayWHEN@Month =2AND@DayOfWeek =1AND@DayOccurrence =3THEN0--Memorial DayWHEN@Month =5AND@DayOfWeek =1AND@Day BETWEEN25AND31THEN0--Independence DayWHEN@Month =7AND@Day =4THEN0--Labor DayWHEN@Month =9AND@DayOfWeek =1AND@DayOccurrence =1THEN0--Columbus DayWHEN@Month =10AND@DayOfWeek =1AND@DayOccurrence =2THEN0--Veterans DayWHEN@Month =11AND@Day =11THEN0--ThanksgivingWHEN@Month =11AND@DayOfWeek =4AND@DayOccurrence =4THEN0--ChristmasWHEN@Month =12AND@Day =25THEN0ELSE1ENDAS Result);SET@TestDate = DATEADD(dd,1,@TestDate);ENDRETURN@TotalBusinessDays;END
Odpowiedzi:
W dni robocze, od poniedziałku do piątku, możesz to zrobić za pomocą pojedynczego WYBORU, na przykład:
Jeśli chcesz uwzględnić święta, musisz trochę popracować ...
źródło
-(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)
+(CASE WHEN DATENAME(dw, @StartDate) = 'Saturday' THEN 1 ELSE 0 END) - (CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)
W rozdziale Obliczanie dni pracy można znaleźć dobry artykuł na ten temat, ale jak widać nie jest on aż tak zaawansowany.
Jeśli chcesz użyć kalendarza niestandardowego, może być konieczne dodanie kontroli i parametrów. Miejmy nadzieję, że będzie to dobry punkt wyjścia.
źródło
Wszystkie podziękowania dla Bogdana Maxima i Petera Mortensena. To jest ich post, właśnie dodałem święta do funkcji (przy założeniu, że masz tabelę „tblHolidays” z polem daty i godziny „HolDate”.
źródło
WHERE HolDate BETWEEN @StartDate AND @EndDate AND DATEPART(dw, HolDate) BETWEEN 2 AND 6
aby liczone były tylko dni wolne od poniedziałku do piątku.Innym podejściem do obliczania dni roboczych jest użycie pętli WHILE, która w zasadzie iteruje zakres dat i zwiększa go o 1 za każdym razem, gdy znajdzie się dzień przypadający na poniedziałek - piątek. Pełny skrypt do obliczania dni roboczych za pomocą pętli WHILE przedstawiono poniżej:
Chociaż opcja pętli WHILE jest czystsza i wykorzystuje mniej wierszy kodu, może stanowić wąskie gardło wydajności w Twoim środowisku, szczególnie gdy zakres dat obejmuje kilka lat.
Możesz zobaczyć więcej metod obliczania dni roboczych i godzin w tym artykule: https://www.sqlshack.com/how-to-calculate-work-days-and-hours-in-sql-server/
źródło
Moja wersja zaakceptowanej odpowiedzi jako funkcji używającej
DATEPART
, więc nie muszę robić porównania ciągów w linii zW każdym razie, oto moja funkcja z datą biznesową
źródło
źródło
(O kilka punktów brakuje mi uprawnień do komentowania)
Jeśli zdecydujesz się zrezygnować z +1 dnia w eleganckim rozwiązaniu CMS , pamiętaj, że jeśli data rozpoczęcia i data zakończenia przypadają na ten sam weekend, otrzymasz odpowiedź negatywną. Tj., 2008/10/26 do 2008/10/26 zwraca -1.
moje raczej uproszczone rozwiązanie:
.. co również ustawia wszystkie błędne posty z datą rozpoczęcia po dacie zakończenia na zero. Coś, czego możesz szukać lub nie.
źródło
Dla różnicy między datami obejmującymi święta poszedłem w ten sposób:
1) Tabela ze świętami:
2) Miałem taką tabelę planowania i chciałem wypełnić pustą kolumnę Work_Days:
3) Aby więc „Work_Days” później wypełnić moją kolumnę, wystarczyło:
Mam nadzieję, że mogę pomóc.
Twoje zdrowie
źródło
Oto wersja, która działa dobrze (tak mi się wydaje). Tabela dni wolnych zawiera kolumny Data_urlopu, które zawierają święta obchodzone w firmie.
źródło
Wiem, że to stare pytanie, ale potrzebowałem wzoru na dni robocze bez daty początkowej, ponieważ mam kilka pozycji i potrzebuję dni, aby poprawnie się zakumulować.
Żadna z nie-iteracyjnych odpowiedzi nie działała dla mnie.
Użyłem definicji podobnej
(inni mogą liczyć północ do soboty zamiast poniedziałku)
Skończyło się na tej formule
źródło
@StartDate
jest sobota czy piątek. Oto moja wersja: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
To jest w zasadzie odpowiedź CMS bez polegania na konkretnym ustawieniu językowym. A ponieważ robimy zdjęcia generyczne, oznacza to, że powinno działać również dla wszystkich
@@datefirst
ustawień.datediff(week, ...)
zawsze używa granicy od soboty do niedzieli przez tygodnie, więc wyrażenie jest deterministyczne i nie trzeba go modyfikować (o ile nasza definicja dni tygodnia jest konsekwentnie od poniedziałku do piątku). Numeracja dni różni się w zależności od@@datefirst
ustawienia i zmodyfikowane obliczenia obsługują tę korektę z niewielką komplikacją związaną z arytmetyką modularną.Czystszym sposobem radzenia sobie z sobotą / niedzielą jest przetłumaczenie dat przed wyodrębnieniem wartości dnia tygodnia. Po przesunięciu wartości wrócą do stałej (i prawdopodobnie bardziej znanej) numeracji, która zaczyna się od 1 w niedzielę i kończy na 7 w sobotę.
Śledziłem tę formę rozwiązania przynajmniej do 2002 roku i artykułu Itzika Ben-Gana. ( https://technet.microsoft.com/en-us/library/aa175781(v=sql.80).aspx ) Chociaż wymagało to niewielkiej zmiany, ponieważ nowsze
date
typy nie pozwalają na arytmetykę dat, poza tym jest identyczny.EDYCJA: dodałem z powrotem to
+1
, co zostało w jakiś sposób pominięte. Warto również zauważyć, że ta metoda zawsze liczy dzień rozpoczęcia i zakończenia. Zakłada się również, że data zakończenia przypada w dniu rozpoczęcia lub po niej.źródło
Korzystanie z tabeli dat:
Jeśli tego nie masz, możesz użyć tabeli liczb:
Oba powinny być szybkie i eliminuje niejednoznaczność / złożoność. Pierwsza opcja jest najlepsza, ale jeśli nie masz tabeli kalendarza, zawsze możesz utworzyć tabelę liczb z CTE.
źródło
źródło
źródło
Wziąłem tutaj różne przykłady, ale w mojej konkretnej sytuacji mamy @PromisedDate do dostawy i @ReceivedDate do faktycznego odbioru przedmiotu. Gdy element został odebrany przed „PromisedDate”, obliczenia nie były sumowane poprawnie, chyba że zamówiłem daty przekazane do funkcji według kolejności kalendarza. Nie chcąc za każdym razem sprawdzać dat, zmieniłem funkcję, żeby sobie z tym poradzić.
źródło
Jeśli potrzebujesz dodać dni robocze do podanej daty, możesz stworzyć funkcję zależną od tabeli kalendarza, opisaną poniżej:
źródło
Podobnie jak w przypadku DATEDIFF, nie uważam daty końcowej za część interwału. Liczba (na przykład) niedziel między @StartDate a @EndDate to liczba niedziel między „początkowym” poniedziałkiem a @EndDate minus liczba niedziel między tym „początkowym” poniedziałek a @StartDate. Wiedząc o tym, możemy obliczyć liczbę dni roboczych w następujący sposób:
Z poważaniem!
źródło
To działa dla mnie, w moim kraju w sobotę i niedzielę są dni wolne od pracy.
Dla mnie ważny jest czas @StartDate i @EndDate.
źródło
Utwórz funkcję taką jak:
Możesz wywołać funkcję taką jak:
Lub jak:
źródło
Koniec
źródło
Poniższy TSQL uznałem za dość eleganckie rozwiązanie (nie mam uprawnień do uruchamiania funkcji). Znalazłem
DATEDIFF
ignorowaniaDATEFIRST
i chciałem, aby mój pierwszy dzień tygodnia był w poniedziałek. Chciałem też, aby pierwszy dzień roboczy był ustawiony na zero i jeśli wypadnie w weekend, poniedziałek będzie równy zero. Może to pomóc komuś, kto ma nieco inne wymagania :)Nie obsługuje dni wolnych od pracy
źródło
Jednym ze sposobów jest „przechodzenie po datach” od początku do końca w połączeniu z wyrażeniem przypadku, które sprawdza, czy dzień nie jest sobotą lub niedzielą, i zaznacza go (1 dla dnia tygodnia, 0 dla weekendu). I na koniec wystarczy suma flag (byłaby równa liczbie 1 flag, ponieważ druga flaga to 0), aby uzyskać liczbę dni tygodnia.
Możesz użyć funkcji narzędziowej typu GetNums (startNumber, endNumber), która generuje serię liczb dla „zapętlenia” od daty początkowej do końcowej. Zobacz http://tsql.solidq.com/SourceCodes/GetNums.txt, aby zapoznać się z implementacją. Logikę można również rozszerzyć, aby obsłużyć święta (powiedzmy, jeśli masz świąteczny stół)
źródło
Kilka pomysłów pożyczyłem od innych, aby stworzyć swoje rozwiązanie. Używam kodu wbudowanego, aby ignorować weekendy i święta federalne w USA. W moim środowisku EndDate może mieć wartość null, ale nigdy nie będzie poprzedzać StartDate.
źródło