How to get the Starting date and ending date of weeks using week number in my case using SQL Server? - sql

I already find to get the week number but, that shows the date in different way.
Actually When I check the week number for the Date of
select datepart(wk,'2016-01-02') //Saturday
output: 1
select datepart(wk,'2016-01-03') //Sunday
output: 2
But when I get the date of starting and ending date of weeks using week number, It shows different.
DECLARE #weekStart INT
DECLARE #weekEnd INT
DECLARE #Year INT
set #weekStart = 1
set #Year = 2016
DECLARE #WeekStartDate date
DECLARE #WeekEndDate date
SET #WeekStartDate = convert(date,DATEADD (WEEK, #weekStart, DATEADD (YEAR, #Year-1900, 0)) - 4 -
DATEPART(DW, DATEADD (WEEK, #weekStart, DATEADD (YEAR, #Year-1900, 0)) - 4) + 1)
SET #WeekEndDate =convert(date,DATEADD (WEEK, #weekStart+1, DATEADD (YEAR, #Year-1900, 0)) - 4 -
DATEPART(DW, DATEADD (WEEK, #weekStart+1, DATEADD (YEAR, #Year-1900, 0)) - 4) + 1)
select #WeekStartDate,#WeekEndDate
output:
StartingDate EndingDate
--------------------------
2016-01-03 2016-01-09
I am expecting output is, if I give week = 1 it should give StartingDate = **2016-01-01 and **EndingDate =2016-01-02
for the week number 2, it should give 2016-01-03 2016-01-09

You can get the absolute start/end for the week and then adjust for the current year the first week start and the last week end. You only really need to calculate the start because the end is start + 6 days:
create procedure spWeekDates #year int, #week int
as
declare #firstWeekDay int
declare #yearStart datetime, #weekStartDate datetime, #weekEndDate datetime
set datefirst 7 -- change as needed
set #yearStart=cast(#year as char(4))+'0101' -- years always start on 01/01 [citation needed]
set #firstWeekDay=datepart(weekday,#yearStart)
-- absolute start/end dates
set #weekStartDate=dateadd(week,#week-1,#yearStart)-#firstWeekDay+1
set #weekEndDate=dateadd(day,6,#weekStartDate)
-- adjusting for target year
if year(#weekStartDate)<#year set #weekStartDate=#yearStart
if year(#weekEndDate)>#year set #weekEndDate=cast(#year as char(4))+'1231'
select #weekStartDate as WeekStartDate, #weekEndDate as WeekEndDate
go
exec spWeekDates 2016,1
exec spWeekDates 2016,2
exec spWeekDates 2016,53
go
Results:
| WeekStartDate | WeekEndDate |
|---------------------|---------------------|
| 2016-01-01 00:00:00 | 2016-01-02 00:00:00 |
| WeekStartDate | WeekEndDate |
|---------------------|---------------------|
| 2016-01-03 00:00:00 | 2016-01-09 00:00:00 |
| WeekStartDate | WeekEndDate |
|---------------------|---------------------|
| 2016-12-25 00:00:00 | 2016-12-31 00:00:00 |

You have to get day of the week of first day in year
if it doesn't equals 1 then you have to set #weekStart = #weekStart -1
and set #StartingDate = first day in year

Related

How to Convert a Date Span to Monthly Records using SQL

I have multiple date spans for the user over a period of few months, I would like to split each span to multiple rows by month and year(default to first day of the month) for which user has been active during the span period. Active user will have future end date records to be split up until the current month and year
Existing Data
ID
Start date
end date
1234
2019-01-01
2019-03-31
1234
2019-09-18
2020-01-31
1234
2022-11-15
2025-01-31
Tried to place the below date month query into the spans
Select Top 500 mmdd=cast (dateadd(Month,-1+Row_Number() Over (Order By (Select NULL)),'2019-01-01') as date)
From master..spt_values n1
order by 1 asc
EXPECTED OUTPUT
ID
active month
1234
2019-01-01
1234
2019-02-01
1234
2019-03-01
1234
2019-09-01
1234
2019-10-01
1234
2019-11-01
1234
2019-12-01
1234
2020-01-01
1234
2022-11-01
1234
2022-12-01
1234
2023-01-01
Larnu is on the right track. One of the easiest ways I've found is to create a calendar table or a function (which can effectively do the same thing).
Try this:
CREATE FUNCTION [dbo].[udfCalendar]
(
#StartDate Date,
#EndDate Date
)
RETURNS #Calendar TABLE (ID int, DateValue DateTime, DayValue int, MonthValue int, YearValue int)
AS
BEGIN
WHILE #StartDate < #EndDate
BEGIN
INSERT #Calendar
SELECT --like 20190101, 1/1/2019, 1, 1, 2019
YEAR (#StartDate) * 10000 + MONTH (#StartDate) * 100 + Day (#StartDate) AS ID,
#StartDate AS DateValue,
DATEPART (dd, #StartDate) AS DayValue,
DATEPART (mm, #StartDate) AS MonthValue,
DATEPART (yy, #StartDate) AS YearValue;
SET #StartDate = DateAdd(m, 1, #StartDate);
END
RETURN;
END
Then you can join to it
Select n1.ID, cal.DateValue as ActiveMonth
From master..spt_values n1 inner join
dbo.udfCalendar('1/1/2019', '1/1/2023') cal
On cal.DateValue Between n1.StartDate and n1.EndDate
Order By DateValue

SQL - How to determine fiscal quarter when quarters are determined by # of days?

I have a situation I've never seen before, where a fiscal year started on 2/4/2018 is broken down like this:
Q1 - 111 days long (2/4/2018 - 5/26/2018)
Q2 - 83 days long (5/27/2018 - 8/18/2018)
Q3 - 83 days long (8/19/2018 - 11/10/2018)
Q4 - 83 days long (11/11/2018 - 2/2/2019)
That is considered FY 2018
The next year, FY 2019, starts on 2/3/2019 and has the same quarter lengths, and Q4 would end on 2/1/2020. FY 2020 then starts on 2/2/2020.
I need to be able to determine the fiscal year and quarter for a given date (#testdate in my code below). The following works for FY 2018:
declare #startdate date = '2/4/2018'
, #startyear int = 2018
, #testdate date = '2/5/2018'
select 'Fiscal Year' = case when datediff(dd, #startdate, #testdate) between 0 and 363 then #startyear else 0 end
select 'Fiscal Quarter' = case when datediff(dd, #startdate, #testdate) between 0 and 111 then 'Q1'
when datediff(dd, #startdate, #testdate) between 112 and 195 then 'Q2'
when datediff(dd, #startdate, #testdate) between 196 and 279 then 'Q3'
when datediff(dd, #startdate, #testdate) between 280 and 363 then 'Q4'
else 'Q0' end
The problem is when I have a date after the end of FY 2018 Q4 (2/2/2019). I'm not sure how to get any date past that to automatically fall into the day ranges (0-111, 112-195, 196-279, 280-363). Manually, for FY 2019, I can subtract 364 from the date and that seems to work. For FY 2020, I can subtract 364 * 2 (728). For each year after that, keep subtracting 364 * n where n is the number of years between whatever future fiscal year and FY 2018.
This works for FY 2020:
declare #startdate date = '2/4/2018'
, #startyear int = 2018
, #testdate date = '5/28/2020'
, #testdate2 date = null
set #testdate2 = dateadd(d, -728, #testdate)
select 'Fiscal Year' = case when datediff(dd, #startdate, #testdate2) between 0 and 363 then #startyear + 2 else 0 end
select 'Fiscal Quarter' = case when datediff(dd, #startdate, #testdate2) between 0 and 111 then 'Q1'
when datediff(dd, #startdate, #testdate2) between 112 and 195 then 'Q2'
when datediff(dd, #startdate, #testdate2) between 196 and 279 then 'Q3'
when datediff(dd, #startdate, #testdate2) between 280 and 363 then 'Q4'
else 'Q0' end
I'm guessing the solution revolves around how to calculate that -728 automatically (which would be 364 * n), and how to increment #startyear accordingly.
Any ideas on how to determine the fiscal year and quarter for a given date with these odd fiscal quarters?
Thanks!
Here is a solution which does an integer division to get the number of years after 2018. We then subtract 364 times this number from DATEDIFF the base year to get the number of days from the start of the current tax year. We can use your case statement to determine the quarter.
create function dbo.quarter (#date date)
returns varchar(100)
as
begin
declare #days int = datediff(dd,'2018-04-02',#date)
declare #years int = #days / 364
set #days = #days - (#years * 364)
set #years = #years + 2018
declare #quarter char(2)= case
when #days between 0 and 111 then 'Q1'
when #days between 112 and 195 then 'Q2'
when #days between 196 and 279 then 'Q3'
when #days between 280 and 363 then 'Q4'
else 'Q0' end
return concat(#years,'-', #quarter)
end
GO
✓
select dbo.quarter('5/4/2018') quarter;
select dbo.quarter('2020-04-02') quarter;
select dbo.quarter('2022-01-24') quarter;
select dbo.quarter(GETDATE()) quarter;
select dbo.quarter('2021-12-25') quarter;
GO
| quarter |
| :------ |
| 2018-Q1 |
| quarter |
| :------ |
| 2020-Q1 |
| quarter |
| :------ |
| 2021-Q4 |
| quarter |
| :------ |
| 2021-Q4 |
| quarter |
| :------ |
| 2021-Q3 |
db<>fiddle here

How to select records which have date range with leap years?

I have table with struct like
ID | Name | StartDate | EndDate
How to select records which have date range, which contains leap years? For example, records, which have date range from 2011-01-01 (StartDate) to 2013-01-01 (EndDate)? This range contains leap years. I use MS SQL.
Thank you.
You could create a table LeapYears, then the query is simple as:
SELECT t.*
FROM TableName t
WHERE EXISTS
(
SELECT 1 FROM LeapYears ly
WHERE ly.[Date] >= StartDate AND ly.[Date] <= EndDate
)
To create a LeapYear table you could use this:
DECLARE #startYear int
DECLARE #endYear int
SET #startYear = 2000
SET #endYear = 2030
CREATE TABLE LeapYears(ID int IDENTITY(1,1), [Date] DATETIME)
DECLARE #currentYear int
SET #currentYear = #startYear
WHILE #currentYear <= #endYear
BEGIN
DECLARE #yearStr char(4)
SELECT #yearStr = CAST(#currentYear AS char(4))
IF ISDATE(#yearStr + '0229') = 1
INSERT INTO LeapYears VALUES(#yearStr + '0229')
SET #currentYear = #currentYear + 1
END
The script creates these records:
ID Date
1 2000-02-29 00:00:00.000
2 2004-02-29 00:00:00.000
3 2008-02-29 00:00:00.000
4 2012-02-29 00:00:00.000
5 2016-02-29 00:00:00.000
6 2020-02-29 00:00:00.000
7 2024-02-29 00:00:00.000
8 2028-02-29 00:00:00.000
I suggest to you to write one stored procedure that receives the two datetime and verify if each year of period is leap year. To do this you can simple do year mod 4 = 0.

SQL: get next relative day of week. (Next Monday, Tuesday, Wed.....)

What I need is a date for the next given day (Monday, Tuesday, Wed...) following today's date.
The user is allowed to select what day following they want and that is stored as an int in a table. "Call me next Tuesday (3)"
Sunday = 1
Monday = 2
Tuesday = 3
...
So my table looks like this.
UserID, NextDayID
What I have come up with is:
select dateadd(dd,(7 - datepart(dw,GETDATE()) + NextDayID ) % 7, getdate())
It seems to work and will return today's date if you ask for the next whatever day today is which I can add a week if needed.
What I am wondering is, is that a good solution or is there something that I'm missing?
1) Your solution uses a non-deterministic function: datepart(dw...) . Because of this aspect, changing DATEFIRST setting will gives different results. For example, you should try:
SET DATEFIRST 7;
your solution;
and then
SET DATEFIRST 1;
your solution;
2) Following solution is independent of DATEFIRST/LANGUAGE settings:
DECLARE #NextDayID INT = 0 -- 0=Mon, 1=Tue, 2 = Wed, ..., 5=Sat, 6=Sun
SELECT DATEADD(DAY, (DATEDIFF(DAY, #NextDayID, GETDATE()) / 7) * 7 + 7, #NextDayID) AS NextDay
Result:
NextDay
-----------------------
2013-09-23 00:00:00.000
This solution is based on following property of DATETIME type:
Day 0 = 19000101 = Mon
Day 1 = 19000102 = Tue
Day 2 = 19000103 = Wed
...
Day 5 = 19000106 = Sat
Day 6 = 19000107 = Sun
So, converting INT value 0 to DATETIME gives 19000101.
If you want to find the next Wednesday then you should start from day 2 (19000103/Wed), compute days between day 2 and current day (20130921; 41534 days), divide by 7 (in order to get number of full weeks; 5933 weeks), multiple by 7 (41531 fays; in order to get the number of days - full weeks between the first Wednesday/19000103 and the last Wednesday) and then add 7 days (one week; 41538 days; in order to get following Wednesday). Add this number (41538 days) to the starting date: 19000103.
Note: my current date is 20130921.
Edit #1:
DECLARE #NextDayID INT;
SET #NextDayID = 1; -- Next Sunday
SELECT DATEADD(DAY, (DATEDIFF(DAY, ((#NextDayID + 5) % 7), GETDATE()) / 7) * 7 + 7, ((#NextDayID + 5) % 7)) AS NextDay
Result:
NextDay
-----------------------
2013-09-29 00:00:00.000
Note: my current date is 20130923.
A calendar table is an alternative to using a bunch of date functions and date arithmetic. A minimal calendar table for this particular problem might look something like this.
2013-09-20 Fri
2012-09-21 Sat
2012-09-22 Sun
2012-09-23 Mon
2012-09-24 Tue
...
So a query to get the next Monday might look like this.
select min(cal_date)
from calendar
where cal_date > current_date
and day_of_week = 'Mon';
In practice, you'll probably want a lot more columns in the calendar table, because you'll find a lot of uses for it.
Also, code that uses a calendar table can usually be seen to be obviously correct. Reading the code above is simple: select the minimum calendar date that's after today and that falls on Monday. It's pretty rare to see code that relies on date functions and date arithmetic that's obviously correct.
A calendar table in PostgreSQL
It's an old question. But I'm sure that posting better solution worth it.
-- 0 = 1st Mon, 1 = 1st Tue, 2 = 1st Wed, ..., 5 = 1st Sat, 6 = 1st Sun
-- 7 = 2nd Mon, 8 = 2nd Tue, ...
declare #NextDayID int = 0, #Date date = getdate()
select cast (cast (
-- last Monday before [Date] inclusive, starting from 1900-01-01
datediff (day, #NextDayID % 7, #Date) / 7 * 7
-- shift on required number of days
+ #NextDayID + 7
as datetime) as date)
This solution is improved solution of #Bogdan Sahlean.
It can operate #NextDayID that is greater than 6.
So you can, for example, find 2nd Monday from today.
Following query shows that my solution works correctly.
select [Date]
, convert (char(5), [0], 10) as Mon1
, convert (char(5), [1], 10) as Tue1
, convert (char(5), [2], 10) as Wed1
, convert (char(5), [3], 10) as Thu1
, convert (char(5), [4], 10) as Fri1
, convert (char(5), [5], 10) as Sat1
, convert (char(5), [6], 10) as Sun1
, convert (char(5), [7], 10) as Mon2
, convert (char(5), [8], 10) as Tue2
from (
select [Date], NextDayID
, cast (cast (
datediff (day, NextDayID % 7, [Date]) / 7 * 7 -- last Monday before [Date] inclusive, starting from 1900-01-01
+ NextDayID + 7 -- shift on required number of days
as datetime) as date) as NextDay
from (
select datefromparts (2018, 5, dt) as [Date]
from (values(14),(15),(16),(17),(18),(19),(20))t_(dt)
) d
cross join (values(0),(1),(2),(3),(4),(5),(6),(7),(8))nd(NextDayID)
) t
pivot (
min (NextDay) for NextDayID in ([0], [1], [2], [3], [4], [5], [6], [7], [8])
) pvt
Result:
Date | Mon1 | Tue1 | Wed1 | Thu1 | Fri1 | Sat1 | Sun1 | Mon2 | Tue2
-----------+-------+-------+-------+-------+-------+-------+-------+-------+------
2018-05-14 | 05-21 | 05-15 | 05-16 | 05-17 | 05-18 | 05-19 | 05-20 | 05-28 | 05-22
2018-05-15 | 05-21 | 05-22 | 05-16 | 05-17 | 05-18 | 05-19 | 05-20 | 05-28 | 05-29
2018-05-16 | 05-21 | 05-22 | 05-23 | 05-17 | 05-18 | 05-19 | 05-20 | 05-28 | 05-29
2018-05-17 | 05-21 | 05-22 | 05-23 | 05-24 | 05-18 | 05-19 | 05-20 | 05-28 | 05-29
2018-05-18 | 05-21 | 05-22 | 05-23 | 05-24 | 05-25 | 05-19 | 05-20 | 05-28 | 05-29
2018-05-19 | 05-21 | 05-22 | 05-23 | 05-24 | 05-25 | 05-26 | 05-20 | 05-28 | 05-29
2018-05-20 | 05-21 | 05-22 | 05-23 | 05-24 | 05-25 | 05-26 | 05-27 | 05-28 | 05-29
This solution doesn't depend on ##datefirst.
I think this is the best way of doing of finding the next Monday
CONVERT(VARCHAR(11),DateAdd(DAY,case
when (DateName(WEEKDAY, NextVisitDate) ='Tuesday') Then 6
when (DateName(WEEKDAY, NextVisitDate) ='Wednesday') Then 5
when (DateName(WEEKDAY, NextVisitDate) ='Thursday') Then 4
when (DateName(WEEKDAY, NextVisitDate) ='Friday') Then 3
when (DateName(WEEKDAY, NextVisitDate) ='Saturday') Then 2
when (DateName(WEEKDAY, NextVisitDate) ='Sunday') Then 1
else 0 end, DateAdd(DAY, DateDiff(DAY, 0, NextVisitDate), 0)),106) AS Monday,
Find the next upcoming day including today if today is that day which needs to be found out.
Just make a tweak... Set the variable #weekdayno as follows:
1 = Sunday, 2 = Monday, 3 = Tuesday, 4 = Wednesday, 5 = Thursday, 6 = Friday, 7 = Saturday
DECLARE #weekdayno INT
DECLARE #todayno INT
SET #weekdayno = 2 ---For Monday----
SET #todayno = DATEPART(dw,GETDATE())
SELECT CASE
WHEN (#todayno = #weekdayno)
THEN CONVERT(varchar, GETDATE(), 101)
WHEN (#todayno < #weekdayno)
THEN CONVERT(varchar, (#weekdayno - #todayno + GETDATE()), 101)
WHEN (#todayno > #weekdayno)
then CONVERT(varchar,(GETDATE() - (#todayno - #weekdayno) + 7), 101)
END AS UpcomingOrToday
The following function enables to you generate the table on-the-fly...this is how I usually do it...I don't like the idea of a perm date table...seems unnecessary, but every person and situation are different :-)
CREATE function [dbo].[fxDateTable]
(
#begindate datetime = null
, #enddate datetime = null
)
RETURNS #dates TABLE
(
EventDate datetime primary key not null
)
as
begin
select #enddate = isnull(#enddate, getdate())
select #begindate = isnull(#begindate, dateadd(day, -3, #enddate))
insert #dates
select dateadd(day, number, #begindate)
from
(select distinct number from master.dbo.spt_values
where name is null
) n
where dateadd(day, number, #begindate) < #enddate
return
end
Try this: This will give the date for required weekday in a month.
declare #monthstartdate date='2020-01-01',#monthenddate date='2020-01-31',#weekday char(9)='thursday',#weeknum int=4
; with cte(N,WeekDayName_C,Date_C) as
(select 1,datename(WEEKDAY,#monthstartdate),#monthstartdate
union all
select n+1,datename(WEEKDAY,dateadd(day,n,#monthstartdate)),dateadd(day,n,#monthstartdate) from cte where n<31 and Date_C<=#monthenddate )
select * from (select *,ROW_NUMBER() over (partition by WeekDayName_C order by Date_C asc)Weeknum from cte)a
where WeekDayName_C=#weekday and Weeknum=#weeknum
I made this as a function, in which the procedure uses available streamlined knowledge thus it's, I think, a robust solution.
CREATE FUNCTION [nilnul.time_._dated.date].[NextWeekday]
(
#nextWeekDay int -- sunday as firstday is 1.
)
RETURNS datetime
AS
BEGIN
declare #time datetime;
set #time=getdate();
declare #weekday int;
set #weekday = datepart(weekday, #time) ;
declare #diff int;
set #diff= #nextWeekDay-#weekday;
--modulo 7 bijectively
declare #moduloed int;
set #moduloed = case
when #diff <=0 then #diff+7
else #diff
end;
return dateadd(day, #moduloed, #time);
END
I couldn't resist sharing my version. This is primitive for next business/weekday, but just change the "not in ('Saturday','Sunday')" part to be the day you are looking for inside the next week.
select top 1 [date]=convert(date, [date]),
, DayName = datename(dw, [date])
, Offset = [day]
from (
select [0]=getdate()
, [1]=getdate()+1
, [2]=getdate()+2
, [3]=getdate()+3
, [4]=getdate()+4
, [5]=getdate()+5
, [6]=getdate()+6) PVT
unpivot([date] for [day] in ([0],[1],[2],[3],[4],[5],[6])) as unpvt
where datename(dw,[date]) not in ('Saturday','Sunday') --wut day you lookin for?
and getdate() <> [date] --do you want today to be part of the results?
order by [date] asc

Looping with Summation of Fields and dateTime as a counter SQL

I have a 3 fields ( ID, Price, Date )
and it looks like this..
|ID| |Price| | Date |
--------------------------------------------
|001| |150.00| | 2007-01-01 11:48:18.000 |
|002| |150.00| | 2007-01-01 15:57:19.000 |
|003| |150.00| | 2007-01-02 13:26:12.000 |
|004| |150.00| | 2007-01-03 10:31:14.000 |
etc etc
and I need to display the TOTAL AMOUNT of sales for EACH DAY for a certain period of time.
So when I put January 1 to January 6...
it should be
| Days | Total Sales |
-------------------------------
| January 1 | --some amount |
| January 2 | --some amount |
| January 3 | --some amount |
| January 4 | --some amount |
| January 5 | --some amount |
| January 6 | --some amount |
I just cant figure it out and Im stuck with this code :) ...
DECLARE #StartDate dateTime,#EndDate dateTime, #TotalSales integer
SET #StartDate = '2007-01-02 11:41:19.000'
SET #EndDate = '2007-01-02 11:46:06.000'
SET #TotalSales = 0
while ( #StartDate = '2007-01-02 11:41:19.000' )
BEGIN
--Some codes
END
thanks :)
You don't need a loop, use set operations whenever possible:
DECLARE #StartDate dateTime,#EndDate dateTime
SET #StartDate = convert(DateTime,'2007-01-01 11:41:19.000',102)
SET #EndDate = convert(DateTime,'2007-01-04 11:46:06.000',102)
;WITH CTE AS (
SELECT ID,Price,[Date]
FROM Sales
WHERE [Date] Between #StartDate AND #EndDate
)
SELECT DATENAME( month ,[Date] ) + ' ' + DATENAME( day ,[Date] ) AS Days
, SUM(Price)AS 'Total Sales'
FROM CTE
GROUP BY DATENAME( month ,[Date] ) + ' ' + DATENAME( day ,[Date] )
SQL-Fiddle Demo
This is similar to Tim Schmelter's solution except that it handles for days that do not have any sales data:
DECLARE #StartDate DATETIME, #EndDate DATETIME
SELECT #StartDate = '01-01-2007', #EndDate = '01-06-2007'
;WITH DateRange ([Date]) AS
(
SELECT
#StartDate [Date]
UNION ALL
SELECT
DATEADD(DAY, 1, [Date]) [Date]
FROM
DateRange
WHERE
[Date] < #EndDate
)
SELECT
DATENAME(MONTH, d.[Date]) + ' ' + DATENAME(DAY, d.[Date]) AS Days,
SUM(ISNULL(Price, 0)) AS [Total Sales]
FROM
DateRange d
LEFT JOIN
Sales s
ON
d.[Date] = DATEADD(DAY, DATEDIFF(DAY, 0, s.[Date]), 0)
GROUP BY
DATENAME(MONTH, d.[Date]) + ' ' + DATENAME(DAY, d.[Date])
SQL Fiddle Example