Count number of weeks in between days in SQL server 2005 - sql

I am here again for some help. This is what I am trying to do. I have a function or sp that takes 3 parameters. startdate, enddate, and dayofweek (m, t, w, etc), need to count how many weeks are in between the start and end date.
Example, if I pass startday=2011-05-02, enddate=2011-05-10, and dayofweek as Tuesday, it should count 2, if I pass dayofweek as wednesday then it should count 1.
I am able to work using this code, is there any better way to do this? The ClosingStartDate is being adjusted to the correct start date based on the dayofweek used. So basically, the code is adding 1 week to the runningdate and running date is adjusted according to the week is used.
Any other way to do it without the while..loop.???
declare #NoofPeriods int
declare #runningdate datetime
set #runningdate = #ClosingStartDate
set #NoofPeriods=0
while (#runningdate <= #NextStatementClosingdate)
begin
set #NoofPeriods=#NoofPeriods+1
set #runningdate = Dateadd(day,7,#runningdate)
end

It is already built in to SQL 2005.
SELECT DATEDIFF(week, #startdate, #enddate)

I'm probably not dead on with the SQL Server syntax, but logic like this should work. Basically use modulo-7 arithmetic to determine how many "odd" days you have at the start of the period, and whether these include one of your chosen weekdays (as determined by your dayofweek) or not.
#diffInDays = SELECT DATEDIFF(day, #startdate, #enddate)
#startDateDay = SELECT DATEPART(weekday, #startdate)
set #diffInWholeWeeks = #diffInDays / 7
set #diffRemainderDays = #diffInDays % 7
if #diffRemainderDays >= ( ( #dayofweek - #startDateDay ) % 7 )
begin
set #extraWeek = 1
end
else
begin
set #extraWeek = 0
end
set #answer = #diffInWholeWeeks + #extraWeek

#slothrop's answer is almost dead on, so I am going to base this on that code and upvote that answer.
Okay this comment was wrong (please ignore):
Because days are from 1-7 instead of 0-6, we need to account for it. I do this by subtracting one after our second modulous. I haven't tested all scenarios, but it seems to work.
What was messing me up was the shift of the week. Add 7 to the dayofweek to make sure the mod works.
This should work,
--Setup
DECLARE #dayofweek int = 4
DECLARE #startdate datetime = '5/2/2011'
DECLARE #enddate datetime = '5/10/2011'
--Solution
DECLARE #diffInDays int = DATEDIFF(day, #startdate, #enddate);
DECLARE #startDateDay int = DATEPART(weekday, #startdate);
DECLARE #extraWeek int = 0;
DECLARE #diffInWholeWeeks int = #diffInDays / 7
DECLARE #diffRemainderDays int = #diffInDays % 7
if #diffRemainderDays >= ( ( #dayofweek + 7 - #startDateDay ) % 7 )
set #extraWeek = 1
DECLARE #answer int = #diffInWholeWeeks + #extraWeek
SELECT #answer
Note: I'm not sure the inline setting of declarations is supported in 2005. If so, just use a SET or SELECT statement to set the values.

Related

How to get database values by week

I just want to ask how can I achieve getting the records in a database by week in a month? using sql
What I really mean is when I input 1 it will get the records in 1st week of a month
I've done a lot of research today but it seems I can't find a good solution
Heres the code:
DECLARE #MONTH int
DECLARE #YEAR int
DECLARE #WEEK int
SET #MONTH = 9
SET #YEAR = 2013
SET #WEEK = 1
SELECT RH.RepairOrderNumber FROM dbo.RepairOrderHeader RH
WHERE MONTH(RH.DateReleased) = #MONTH AND YEAR(RH.DateReleased) = #YEAR AND WEEK(RH.DateReleased) = #WEEK
I just want to fetched the records according to month,year, and by week is there any way and precise code on how to do this?
DATEPART(WEEK,) gives you number of a week in an YEAR so you just need to calculate number of week in the month calculating difference between current date week number and the week number of the first day of the month:
WHERE MONTH(RH.DateReleased) = #MONTH
AND YEAR(RH.DateReleased) = #YEAR
AND DATEPART(WEEK,RH.DateReleased)
-DATEPART(WEEK,DATEADD(DAY,-DAY(RH.DateReleased)+1,RH.DateReleased))+1
= #WEEK
WEEK returns the week of the year, starting on a constant day of the week (i.e. each Sunday). You seem to want the week of the month, which I guess would always start on the 1st of each month.
so:
SELECT RH.RepairOrderNumber FROM dbo.RepairOrderHeader RH
WHERE MONTH(RH.DateReleased) = #MONTH AND
YEAR(RH.DateReleased) = #YEAR AND
DAY(RH.DateReleased) / 7 + 1 = #WEEK
Where possible avoid using functions on columns in the WHERE clause. You immediately lose the ability to take advantage of indices on these columns. The below solution gives the same results as the select answer, but performs much better:
DECLARE #MONTH INT = 9
DECLARE #YEAR INT = 2013
DECLARE #WEEK INT = 2
--1ST OF THE MONTH DECLARED
DECLARE #Date DATE = CAST(CAST(#Year AS VARCHAR(4)) +
RIGHT('0' + CAST(#Month AS VARCHAR(2)), 2) + '01' AS DATE)
SET #Date = DATEADD(WEEK, #Week - 1, DATEADD(DAY, 1 - DATEPART(WEEKDAY, #Date), #Date));
SELECT RH.RepairOrderNumber
FROM dbo.RepairOrderHeader RH
WHERE RH.DateReleased >= #Date
AND RH.DateReleased < DATEADD(WEEK, 1, #Date);
All this does is get the first of the month in question, then find the first day of the week that the first of the month falls in. Then adds the declared number of weeks to this date.
With an example table with 6 years of data in and 10 rows for each day (20,480 records, so not a lot) and an index on your date column the execution plans of this and the accepted answer are as follows (Accepted on top, using dates on the bottom):
Testing DDL and queries on SQL-Fiddle
This shows that when you use WEEK(DateColumn) = #Week that the index is not used at all, and the query cost is much higher. Depending on the distribution of data in your table this could make a massive difference, even in the small set of data in my example it is 800% more efficient.
I consider '2013-08-26' - '2013-09-01' the first week in september
DECLARE #MONTH int = 9
DECLARE #YEAR int = 2013
DECLARE #WEEK int = 1
DECLARE #from date= dateadd(day, datediff(day, -(#WEEK - 1)*7,
(dateadd(month, (#year-1900) * 12 + #month - 1 , 0)))/7*7 , 0)
SELECT RH.RepairOrderNumber
FROM dbo.RepairOrderHeader RH
WHERE RH.DateReleased >= #from
and RH.DateReleased < dateadd(week, 1, #from)

SQL Server UDF for getting week of year, with first day of week argument

I'm looking for a SQL Server UDF that will be equivalent to DATEPART(WEEK, #date), but will allow the caller to specify the first day of the week. Somewhat similar to MySql's WEEK function. E.g.:
CREATE FUNCTION Week (#date date, #firstdayofweek int)
RETURNS int
BEGIN
-- return result would be the same as:
-- SET DATEFIRST #firstdayofweek
-- DATEPART(WEEK, #date)
END
My application does not have the opportunity to call SET DATEFIRST.
Examples:
SELECT Week('2013-08-28', 2) -- returns 35
SELECT Week('2013-08-28', 3) -- returns 36
The above results would always be the same, regardless of SQL Server's value for ##DATEFIRST.
You could use something like this:
DATEPART(WEEK, DATEADD(DAY, 8 - #firstdayofweek, #date))
Instead of moving the first day of the week you are moving the actual date. Using this formula the first day of the week would be set using the same number values for days that MS SQL Server uses. (Sunday = 1, Saturday = 7)
I've found a couple of articles that helped me answer to derive an answer to this question:
Deterministic scalar function to get week of year for a date
http://sqlmag.com/t-sql/datetime-calculations-part-3
It may be possible to simplify this UDF, but it gives me exactly what I was looking for:
CREATE FUNCTION Week (#date DATETIME, #dateFirst INT)
RETURNS INT
BEGIN
DECLARE #normalizedWeekOfYear INT = DATEDIFF(WEEK, DATEADD(YEAR, DATEDIFF(YEAR, 0, #date), 0), #date) + 1
DECLARE #jan1DayOfWeek INT = DATEPART(WEEKDAY, DATEADD(YEAR, DATEDIFF(YEAR, 0, #date), 0) + ##DATEFIRST- 7) - 1
DECLARE #dateDayOfWeek INT = DATEPART(WEEKDAY, DATEADD(DAY, ##DATEFIRST- 7, #date)) - 1
RETURN #normalizedWeekOfYear +
CASE
WHEN #jan1DayOfWeek < #dateFirst AND #dateDayOfWeek >= #dateFirst THEN 1
WHEN #jan1DayOfWeek >= #dateFirst AND #dateDayOfWeek < #dateFirst THEN -1
ELSE 0
END
END
GO
Then, executing the following statements would return 35 and 36 respectively:
SELECT dbo.Week('2013-08-28', 2)
SELECT dbo.Week('2013-08-28', 3)
/*
No matter how ##DATEFIRST is
return result as
weekdayName,weekdayNumber
Mo 1
Tu 2
Wn 3
Th 4
Fr 5
Sa 6
Su 7
*/
CREATE FUNCTION dbo.fnFixWeekday
(
#currentDate date
)
RETURNS INT
AS
BEGIN
-- get DATEFIRST setting
DECLARE #ds int = ##DATEFIRST
-- get week day number under current DATEFIRST setting
DECLARE #dow int = DATEPART(dw,#currentDate)
RETURN 1+(((#dow+#ds) % 7)+5) % 7
END

Returning Database values dependent on days not dates

Hi I'm trying to get a database to return some values, specifically, I need to return every invoice which is dated between the previous Monday to Sunday.
The reason for this is that this query will be ran on varying days, eg I need to return the results for the week Monday 5th August - Sunday 11th August at some point in the following week (12th - 18th).
I originally had this set up as a simple WHERE:
[WHERE TaxDate >= (GETDATE() - 7)]
because the invoices were done every monday so it worked itself out, but now they could be done any day of the following week. Are there any functions that I can use to do this?
Thanks
Look at the datepart function with the day of week parameter:
WHERE Datepart(dw, TaxDate) = 2
declare #currentdow int
declare #delta int
declare #startdate datetime
declare #endtime datetime
set #delta = 0
set #startofweek = DatePart(dw, getDate())
while #scurrentdow <> 2
begin
set #delta = #delta + 1
set #currentdow = #currentdow - 1
if #currentdow < 1
begin
set #currentdow = 7
end
end
set #startdate = DateSubtract(day,GetDate(), delta)
set #enddate = DateAdd(day, #startdate, 6)
long winded, but close I think.

How do I add to some date excluding weekends using SQL Server?

For example:
#dtBegin = '2012-06-29'
#input = 20
I want the output to be '2012-07-27'.
I had to tackle the same problem in my project. Gordon Lionoff's solution got me on the right track but did not always produce the right result. I also have to take in account dates that start in a weekend. I.e. adding 1 business day to a saturday or sunday should result in a monday. This is the most common approach to handling business day calculation.
I've created my own solution based on Gordon Linoff's function and Patrick McDonald's excellent C# equivalent
NOTE: My solution only works if DATEFIRST is set to the default value of 7. If you use a different DATEFIRST value, you will have to change the 1, 7 and (1,7,8,9,10) bits.
My solution consists of two functions. An "outer" function that handles edge cases and an "inner" function that performs the actual calculation. Both functions are table-valued functions so they will actually be expanded into the implementing query and fed through the query optimizer.
CREATE FUNCTION [dbo].[UTL_DateAddWorkingDays]
(
#date datetime,
#days int
)
RETURNS TABLE AS RETURN
(
SELECT
CASE
WHEN #days = 0 THEN #date
WHEN DATEPART(dw, #date) = 1 THEN (SELECT Date FROM [dbo].[UTL_DateAddWorkingDays_Inner](DATEADD(d, 1, #date), #days - 1))
WHEN DATEPART(dw, #date) = 7 THEN (SELECT Date FROM [dbo].[UTL_DateAddWorkingDays_Inner](DATEADD(d, 2, #date), #days - 1))
ELSE (SELECT Date FROM [dbo].[UTL_DateAddWorkingDays_Inner](#date, #days))
END AS Date
)
As you can see, the "outer" function handles:
When adding no days, return the original date. This will keep saturday and sunday dates intact.
When adding days to a sunday, start counting from monday. This consumes 1 day.
When adding days to a saturday, start counting from monday. This consumes 1 day.
In all other cases, perform the usual calculation
_
CREATE FUNCTION [dbo].[UTL_DateAddWorkingDays_Inner]
(
#date datetime,
#days int
)
RETURNS TABLE AS RETURN
(
SELECT
DATEADD(d
, (#days / 5) * 7
+ (#days % 5)
+ (CASE WHEN ((#days%5) + DATEPART(dw, #date)) IN (1,7,8,9,10) THEN 2 ELSE 0 END)
, #date) AS Date
)
The "inner" function is similar to Gordon Linoff's solution, except it accounts for dates crossing weekend boundaries but without crossing a full week boundary.
Finally, I created a test script to test my function. The expected values were generated using Patrick McDonald's excellent C# equivalent and I randomly cross-referenced this data with this popular calculator.
You can do this without resorting to a calendar table or user defined function:
dateadd(d,
(#input / 5) * 7 + -- get complete weeks out of the way
mod(#input, 5) + -- extra days
(case when ((#input%5) + datepart(dw, #dtbegin)%7) in (7, 1, 8) or
((#input%5) + datepart(dw, #dtbegin)%7) < (#input%5)
then 2
else 0
end),
#dtbegin
)
I'm not saying this is pretty. But sometimes arithmetic is preferable to a join or a loop.
This is what I've tried:
CREATE function [dbo].[DateAddWorkDay]
(#days int,#FromDate Date)
returns Date
as
begin
declare #result date
set #result = (
select b
from
(
SELECT
b,
(DATEDIFF(dd, a, b))
-(DATEDIFF(wk, a, b) * 2)
-(CASE WHEN DATENAME(dw, a) = 'Sunday' THEN 1 ELSE 0 END)
-(CASE WHEN DATENAME(dw, b) = 'Saturday' THEN 1 ELSE 0 END)
-COUNT(o.Holiday_Date)
as workday
from
(
select
#FromDate as a,
dateadd(DAY,num +#days,#FromDate) as b
from (select row_number() over (order by (select NULL)) as num
from Information_Schema.columns
) t
where num <= 100
) dt
left join Holiday o on o.Holiday_Date between a and b and DATENAME(dw, o.Holiday_Date) not in('Saturday','Sunday')
where DATENAME(dw, b) not in('Saturday','Sunday')
and b not in (select Holiday_Date from OP_Holiday where Holiday_Date between a and b)
group by a,b
) du
where workday =#days
)
return #result
end
Where Holiday is a table with holiday_date as a reference for holiday.
I realize I'm about 8 years late to this party... But I took Martin's answer and updated it to:
1. a single scalar function instead of 2 nested table-valued functions
I tested using his original test script and my function passes too. Also, it seems the rebuild to a scalar function has a slight positive impact on performance. Both versions seem to benefit equally from 'buffer caching', the scalar version performing up to 25% better non-cached and up to 40% better cached. Disclaimer: I just ran both versions a bunch of times and recorded the times, I did not do any decent performance testing.
2. include support for DATEFIRST is either Monday, Saturday or Sunday
I feel a UDF should be agnostic to the datefirst setting. I'm in Europe and Monday is the default here. The original function would not work without adaptation.
According to wikipedia Monday, Saturday and Sunday are the only real world first days of the week. Support for other could easily be added, but would make the code more bulky and I have a hard time envisioning a real world use case.
CREATE FUNCTION dbo.fn_addWorkDays
(
#date datetime,
#days int
)
RETURNS DATETIME
AS
BEGIN
IF NOT ##DATEFIRST IN (1,6,7) BEGIN --UNSUPPORTED DATE FIRST
RETURN NULL
/* MONDAY = FRIST DAY */
END ELSE IF #days = 0 BEGIN
RETURN #date
END ELSE IF ##DATEFIRST = 1 AND DATEPART(dw, #date) = 7 BEGIN --SUNDAY
SET #date = DATEADD(d, 1, #date)
SET #days = #days - 1
END ELSE IF ##DATEFIRST = 1 AND DATEPART(dw, #date) = 6 BEGIN --SATURDAY
SET #date = DATEADD(d, 2, #date)
SET #days = #days - 1
/* SATURDAY = FRIST DAY */
END ELSE IF ##DATEFIRST = 7 AND DATEPART(dw, #date) = 2 BEGIN --SUNDAY
SET #date = DATEADD(d, 1, #date)
SET #days = #days - 1
END ELSE IF ##DATEFIRST = 7 AND DATEPART(dw, #date) = 1 BEGIN --SATURDAY
SET #date = DATEADD(d, 2, #date)
SET #days = #days - 1
/* SUNDAY = FRIST DAY */
END ELSE IF ##DATEFIRST = 7 AND DATEPART(dw, #date) = 1 BEGIN --SUNDAY
SET #date = DATEADD(d, 1, #date)
SET #days = #days - 1
END ELSE IF ##DATEFIRST = 7 AND DATEPART(dw, #date) = 7 BEGIN --SATURDAY
SET #date = DATEADD(d, 2, #date)
SET #days = #days - 1
END
DECLARE #return AS dateTime
SELECT #return =
DATEADD(d
, (#days / 5) * 7
+ (#days % 5)
+ (CASE
/* MONDAY = FRIST DAY */
WHEN ##DATEFIRST = 1 AND ((#days%5) + DATEPART(dw, #date)) IN (6,7,8,9) THEN 2
/* SATURDAY = FRIST DAY */
WHEN ##DATEFIRST = 7 AND ((#days%5) + DATEPART(dw, #date)) IN (1,2,8,9,10) THEN 2
/* SUNDAY = FRIST DAY */
WHEN ##DATEFIRST = 7 AND ((#days%5) + DATEPART(dw, #date)) IN (1,7,8,9,10,11) THEN 2
ELSE 0
END)
, #date)
RETURN #return
END
I hope this might benefit someone!
you can use the below mentioned code for exclude weekends
go
if object_id('UTL_DateAddWorkingDays') is not null
drop function UTL_DateAddWorkingDays
go
create FUNCTION [dbo].[UTL_DateAddWorkingDays]
(
#date datetime,
#daysToAdd int
)
RETURNS date
as
begin
declare #daysToAddCnt int=0,
#Dt datetime
declare #OutTable table
(
id int identity(1,1),
WeekDate date,
DayId int
)
while #daysToAdd>0
begin
set #Dt=dateadd(day,#daysToAddCnt,#date)
--select #daysToAddCnt cnt,#Dt date,DATEPART(weekday,#Dt) dayId,#daysToAdd daystoAdd
if(DATEPART(weekday,#Dt) <>7 and DATEPART(weekday,#Dt)<>1)
begin
insert into #outTable (WeekDate,DayId)
select #Dt,DATEPART(weekday,DATEADD(day,#daysToAddCnt,#Dt))
set #daysToAdd=#daysToAdd-1
end
set #daysToAddCnt=#daysToAddCnt+1
end
select #Dt=max(WeekDate) from #outTable
return #Dt
end
what about this?
declare #d1 date='2012-06-29'
declare #days int=20
select dateadd(dd,#days,#d1)
select dateadd(dd,case DATEPART(dw,t1.d) when 6 then +2 when 7 then +1 else +0 end,t1.d)
from
(
select dateadd(dd,CEILING((convert(float,#days)/5)*2)+#days,#d1)d
)t1
i found how many we in the range by CEILING((convert(float,#days)/5)*2)
then i added them to date and at the end i check if is a saturday or sunday and i add 1 or 2 days.

Microsoft SQL Server 2005 - using the modulo operator

So I have a silly problem, I have not used much SQL Server before, or any SQL for that matter. I basically have a minor mathematical problem that I need solved, and I thought modulo would be good.
I have a number of dates in the database, but I need them be rounded off to the closest [dynamic integer] (could be anything from 0 to 5000000) which will be input as a parameter each time this query is called.
So I thought I'd use modulo to find the remainder, then subtract that remainder from the date.
If there is a better way, or an integrated function, please let me know!
What would be the syntax for that? I've tried a lot of things, but I keep getting error messages like integers/floats/decimals can't be used with the modulo operators. I tried casting to all kinds of numeric datatypes.
Any help would be appreciated.
Create function [GetNDates]
(
#NumberOfDates int,
#StartDate DateTime,
#EndDate DateTime
)
Returns #DatesTable table
(
MyDate DateTime
)
As
Begin
Declare #TotalDays int
Declare #Increment int
Declare #Counter int
Declare #DateCounter DateTime
SET #Counter = 0
SELECT #DateCounter = #StartDate
SELECT #TotalDays = DATEDIFF(day, #StartDate, #EndDate)
SET #Increment = #TotalDays / #NumberOfDates
WHILE #Counter < #NumberOfDates
BEGIN
SET #DateCounter = DATEADD(Day, #Increment, #DateCounter)
INSERT INTO #DatesTable (MyDate) VALUES (#DateCounter)
SET #Counter = #Counter + 1
END
Return
End
GO
select * from dbo.GetNDates(40, '1/1/2010', '12/31/2010')