Convert date and week of day to calendar format using pivot - sql

I have the following data in the temp table
I want it to convert into the calendar format using pivot as
but due to aggregate it only shows 1 row
SELECT
*
FROM
(SELECT
CONVERT(VARCHAR(10), dt, 106) AS Date1,
dw AS Wd
FROM
#tbl) t
PIVOT
(MAX(Date1)
FOR Wd IN ([Sunday], [Monday], [Tuesday], [Wednesday],[Thursday], [Friday], [Saturday])
) AS pivotTable
as per the comments, I have following update
With CTE (dt,dw,last)
as
(
select Cast(dateadd(day, -day(GetDate())+1,GetDate())as date),datename(dw,Cast(dateadd(day, -day(GetDate())+1,GetDate())as date)),
cast(dateadd(day,-1,dateadd(Month,1,Cast(dateadd(day, -day(GetDate())+1,GetDate())as date))) as date)
union all
select cast(dateadd(day,1,dt) as date), DATENAME(DW,cast(dateadd(day,1,dt) as date)),last
from CTE
where cast(dateadd(day,1,dt) as date)<last
)
select * from (
select dt,dw,ROW_NUMBER()over(partition by dw order by dt) as RN
from CTE
)t
pivot(
Max(dt)
for dw in ([Sunday],[Monday],[Tuesday],[Wednesday],[Thursday],[Friday],[Saturday])
) as Pt Option (MAXRECURSION 31)
The problem is that, pivot is not considering the last day of the month for some reason as you can see in the screenshot.

You need to "group by" something unique to the row. In your case, since you want one row per week, that would be the week number.¹ In a pivot table, that "grouping" is achieved by just adding the relevant field to your data source:
SELECT [Sunday],[Monday],[Tuesday],[Wednesday],[Thursday],[Friday],[Saturday]
FROM (SELECT dt, dw, DATEPART(wk, dt) AS week_nr
FROM #tbl
) AS t
PIVOT (MAX(dt)
FOR dw in ([Sunday],[Monday],[Tuesday],[Wednesday],[Thursday],[Friday],[Saturday])
) AS pivotTable
Result:
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
2022-05-01
2022-05-02
2022-05-03
2022-05-04
2022-05-05
2022-05-06
2022-05-07
2022-05-08
2022-05-09
2022-05-10
2022-05-11
2022-05-12
2022-05-13
2022-05-14
Fiddle: http://sqlfiddle.com/#!18/cdf3e1/6/0
¹ Obviously, if you want your calendar to span multiple years, you'll need the "week year" as well.

Got, the solution, the last day was missing because of the where statement in my CTE, it should be less than or equals to max date.
With CTE (dt,dw,last)
as
(
select Cast(dateadd(day, -day(GetDate())+1,GetDate())as date),datename(dw,Cast(dateadd(day, -day(GetDate())+1,GetDate())as date)),
cast(dateadd(day,-1,dateadd(Month,1,Cast(dateadd(day, -day(GetDate())+1,GetDate())as date))) as date)
union all
select cast(dateadd(day,1,dt) as date), DATENAME(DW,cast(dateadd(day,1,dt) as date)),last
from CTE
where cast(dateadd(day,1,dt) as date)<=last
)
select * from (
select dt,dw,ROW_NUMBER()over(partition by dw order by dt) as RN
from CTE
)t
pivot(
Max(dt)
for dw in ([Sunday],[Monday],[Tuesday],[Wednesday],[Thursday],[Friday],[Saturday])
) as Pt Option (MAXRECURSION 31)

Related

Get list of dates that falls in End Of each Month?

I need to get end of each month for the past 3 yrs from the current date excluding statutory holidays and weekends using table1 and table2. Table1 has all the dates ranging from year 2025-2017. Table2 has all the statutory holidays for the years ranging from 2025-2017.
How to create SQL script for to attain this result? Any suggestions could help. Expected result would be list of date last 3yrs of endofmonth excluding statutory holidays and weekends.
Table 1 has 2 columns, DateId and FullDate column
DateID Fulldate
1010392 2019-12-1
1010393 2019-12-2
1010394 2019-12-3
1010395 2019-12-4
.
.
101086 2019-12-31
Table 2 has 2 columns, DateId and Statutory_Holidays
Date ID Stat_Holidays
101085 2019-12-25
101086 2019-12-26
And the returned results should look like
WeekDay_FullDate_Past3yrs
2019-12-31
2020-1-31
2020-2-28
2020-3-31
Tried the below:
select * from
( select a.Date from Table1 a where a.Date <=
'20221215' and a.Date >= DATEADD (YEAR, -3, getdate()) ) as t1
join
( select EOMONTH(a.Date) as Date from Table1 a where a.Date <= '20221215' and a.Date >= DATEADD (YEAR, -3, getdate()) ) as t2 on t1.Date = t2.Date
tried the solution from the below link it dosen't solve my issue. I'm looking to get list of last workday of a month(excluding weekends and holiday) for the past 3yrs
SQL Server - Get Last Business Data excluding holidays and Weekends
You can group by month and year and take the max date (excluding holidays and weekends):
SET DATEFIRST 1;
DECLARE #CurrentDate DATE = '20221215';
WITH cte
AS
(
SELECT MAX(Date ) as EOMDate
FROM Table1
WHERE DATEPART(weekday,Date) NOT IN (6,7)
AND Date NOT IN (SELECT Date FROM Table2)
GROUP BY YEAR(Date),MONTH(Date)
)
SELECT *
FROM cte
WHERE cte.EOMDate BETWEEN DATEADD(YEAR,-3,#CurrentDate) AND #CurrentDate;
This should work and give you the last working day for each month in your main table. Just filter by the desired time period:
SELECT TOP 1 WITH TIES FullDate
FROM Table1
WHERE FullDate NOT IN (SELECT Stat_Holidays FROM Table2) -- not holiday
AND DATEPART(weekday, FullDate) NOT IN (7, 1) -- not saturday and sunday
ORDER BY DENSE_RANK() OVER(PARTITION BY YEAR(FullDate), MONTH(FullDate) ORDER BY FullDate DESC)
Check this with your table name and column names.
select year(dates) _Year ,month(dates) _Month,EOMONTH(dates) endofMOnth from tabledate1 where DATENAME(DW, dates) not in ('Saturday','Sunday')
and EOMONTH(dates) not in (select holidaydate from tableholidays)
Group by year(dates),month(dates),EOMONTH(dates)
order by year(dates) ,month(dates)

SQL Server summarize by week starting monday

I need to summarize a revenue field by week, starting on Monday. My data looks as follows:
Date Rev
+----------+--------
4/10/2017 5
4/11/2017 6
4/12/2017 7
4/13/2017 8
4/17/2017 9
4/19/2017 12
I would like the data to show the first day of the week and the summary of that week like:
Date Rev
+----------+--------
4/10/2017 26
4/17/2017 21
How could I accomplish this?
select DATEADD(week, DATEDIFF(week,0, [date]) , 0) as [date], sum(rev)
from [data]
group by DATEADD(week, DATEDIFF(week,0, [date]) , 0)
order by [date]
SQLFiddle
This maps to your sample data. It works because day 0 (1/1/1900) just happened to fall on a Monday. If you needed to use Sunday or Tuesday as your start of week you would adjust your input dates and offset accordingly.
An example is here, you can use datepart function to easily get the result:
create table #temp
([Date] datetime, Rev int)
insert into #temp values ('2010-01-01',10)
insert into #temp values ('2010-01-02',20)
insert into #temp values ('2010-01-07',60)
insert into #temp values ('2010-01-09',50)
SELECT DATEPART (wk, [Date]) AS Week, Sum(Rev) AS TotalRev
FROM #temp
Group By DATEPART (wk, [Date]);
Edit:
If you are insisting on using the first date value instead of week number, this is an alternative solution for you:
;with cte (firstdateofweek,weekno) as
(
select min([Date]) as firstdateofweek ,DATEPART (wk, [Date]) weekno
from #temp
group by DATEPART (wk, [Date])
)
SELECT cte.firstdateofweek AS Week,sum(Rev) AS Sales
FROM #temp
INNER JOIN cte on DATEPART(wk, [Date]) = cte.weekno
Group By cte.firstdateofweek

get last 3 month on year in sql server

I want to get last 3 months name from current month. For example current month is December. So, I want get like this October, November and December.
This is my query:
SELECT CONVERT(CHAR, DATENAME(MONTH, IssueDate)) AS MonthName, ItemId
FROM dbo.Issue AS Issue
GROUP BY CONVERT(CHAR, DATENAME(MONTH, IssueDate)), ItemId
HAVING (ItemId = 427)
This returns:
But, my need is:
N.B. When December month close and January month open then October auto excluded as like (November, December and January)
this link is my Database only 2 table (size-243 KB with Zip) on the google drive https://goo.gl/S4m0R5
Add a date diff in a where clause to filter to the last 3 months, and then order by the month number at the end:
SELECT CONVERT(CHAR, DATENAME(MONTH, [IssueDate])) AS MonthName, ItemId
FROM [dbo].[Issue] AS Issue
WHERE datediff(m, [IssueDate], getdate()) between 0 and 2
GROUP BY CONVERT(CHAR, DATENAME(MONTH, [IssueDate])), ItemId, MONTH(IssueDate)
HAVING (ItemId= 427)
order by MONTH(IssueDate);
You can use DATEADD function:
WHERE IssueDate >= dateadd( month, -2, dateadd( day, -datepart( day, getdate() ) + 1, cast( getdate() as date ) ) )
That will give you IssueDate >= '2015-10-01' given today.
That will also work with index you have on IssueDate, if you start doing something like DATEADD / DATEDIFF etc. on IssueDate then the index can only be scanned end-to-end because it needs to processs all rows in the table so renders the index significantly less effective.
DECLARE #t TABLE
(
IssueDate DATETIME,
ItemId INT
)
INSERT INTO #t (IssueDate, ItemId)
VALUES
('20160105', 427),
('20151212', 427),
('20151213', 427),
('20151110', 427),
('20151001', 427),
('20150905', 427)
SELECT DATENAME(MONTH, dt)
FROM (
SELECT DISTINCT TOP(3) DATEADD(MONTH, DATEDIFF(MONTH, 0, IssueDate), 0) AS dt
FROM #t
WHERE ItemId = 427
ORDER BY dt DESC
) t
results -
------------------------------
January
December
November
You can use a recursive CTE to get month names for the last 12 months and then limit it to the last 3 month names in the second part of the query:
;WITH months(MonthNumber) AS
(
SELECT 0
UNION ALL
SELECT MonthNumber+1
FROM months
WHERE MonthNumber < 12
)
SELECT DATENAME(MONTH,DATEADD(MONTH,-MonthNumber,GETDATE())) AS [month]
FROM dbo.Issue AS Issue
CROSS JOIN months m
WHERE m.MonthNumber <3
GROUP BY DATENAME(MONTH,DATEADD(MONTH,-MonthNumber,GETDATE())) , ItemId
HAVING (ItemId = 427)

SQL calculate date segments within calendar year

What I need is to calculate the missing time periods within the calendar year given a table such as this in SQL:
DatesTable
|ID|DateStart |DateEnd |
1 NULL NULL
2 2015-1-1 2015-12-31
3 2015-3-1 2015-12-31
4 2015-1-1 2015-9-30
5 2015-1-1 2015-3-31
5 2015-6-1 2015-12-31
6 2015-3-1 2015-6-30
6 2015-7-1 2015-10-31
Expected return would be:
1 2015-1-1 2015-12-31
3 2015-1-1 2015-2-28
4 2015-10-1 2015-12-31
5 2015-4-1 2015-5-31
6 2015-1-1 2015-2-28
6 2015-11-1 2015-12-31
It's essentially work blocks. What I need to show is the part of the calendar year which was NOT worked. So for ID = 3, he worked from 3/1 through the rest of the year. But he did not work from 1/1 till 2/28. That's what I'm looking for.
You can do it using LEAD, LAG window functions available from SQL Server 2012+:
;WITH CTE AS (
SELECT ID,
LAG(DateEnd) OVER (PARTITION BY ID ORDER BY DateEnd) AS PrevEnd,
DateStart,
DateEnd,
LEAD(DateStart) OVER (PARTITION BY ID ORDER BY DateEnd) AS NextStart
FROM DatesTable
)
SELECT ID, DateStart, DateEnd
FROM (
-- Get interval right before current [DateStart, DateEnd] interval
SELECT ID,
CASE
WHEN DateStart IS NULL THEN '20150101'
WHEN DateStart > start THEN start
ELSE NULL
END AS DateStart,
CASE
WHEN DateStart IS NULL THEN '20151231'
WHEN DateStart > start THEN DATEADD(d, -1, DateStart)
ELSE NULL
END AS DateEnd
FROM CTE
CROSS APPLY (SELECT COALESCE(DATEADD(d, 1, PrevEnd), '20150101')) x(start)
-- If there is no next interval then get interval right after current
-- [DateStart, DateEnd] interval (up-to end of year)
UNION ALL
SELECT ID, DATEADD(d, 1, DateEnd) AS DateStart, '20151231' AS DateEnd
FROM CTE
WHERE DateStart IS NOT NULl -- Do not re-examine [Null, Null] interval
AND NextStart IS NULL -- There is no next [DateStart, DateEnd] interval
AND DateEnd < '20151231' -- Current [DateStart, DateEnd] interval
-- does not terminate on 31/12/2015
) AS t
WHERE t.DateStart IS NOT NULL
ORDER BY ID, DateStart
The idea behind the above query is simple: for every [DateStart, DateEnd] interval get 'not worked' interval right before it. If there is no interval following the current interval, then also get successive 'not worked' interval (if any).
Also note that I assume that if DateStart is NULL then DateStart is also NULL for the same ID.
Demo here
If your data is not too big, this approach will work. It expands all the days and ids and then re-groups them:
with d as (
select cast('2015-01-01' as date)
union all
select dateadd(day, 1, d)
from d
where d < cast('2015-12-31' as date)
),
td as (
select *
from d cross join
(select distinct id from t) t
where not exists (select 1
from t t2
where d.d between t2.startdate and t2.enddate
)
)
select id, min(d) as startdate, max(d) as enddate
from (select td.*,
dateadd(day, - row_number() over (partition by id order by d), d) as grp
from td
) td
group by id, grp
order by id, grp;
An alternative method relies on cumulative sums and similar functionality that is much easier to expression in SQL Server 2012+.
Somewhat simpler approach I think.
Basically create a list of dates for all work block ranges (A). Then create a list of dates for the whole year for each ID (B). Then remove the A from B. Compile the remaining list of dates into date ranges for each ID.
DECLARE #startdate DATETIME, #enddate DATETIME
SET #startdate = '2015-01-01'
SET #enddate = '2015-12-31'
--Build date ranges from remaining date list
;WITH dateRange(ID, dates, Grouping)
AS
(
SELECT dt1.id, dt1.Dates, dt1.Dates + row_number() over (order by dt1.id asc, dt1.Dates desc) AS Grouping
FROM
(
--Remove (A) from (B)
SELECT distinct dt.ID, tmp.Dates FROM DatesTable dt
CROSS APPLY
(
--GET (B) here
SELECT DATEADD(DAY, number, #startdate) [Dates]
FROM master..spt_values
WHERE type = 'P' AND DATEADD(DAY, number, #startdate) <= #enddate
) tmp
left join
(
--GET (A) here
SELECT DISTINCT T.Id,
D.Dates
FROM DatesTable AS T
INNER JOIN master..spt_values as N on N.number between 0 and datediff(day, T.DateStart, T.DateEnd)
CROSS APPLY (select dateadd(day, N.number, T.DateStart)) as D(Dates)
WHERE N.type ='P'
) dr
ON dr.Id = dt.Id and dr.Dates = tmp.Dates
WHERE dr.id is null
) dt1
)
SELECT ID, CAST(MIN(Dates) AS DATE) DateStart, CAST(MAX(Dates) AS DATE) DateEnd
FROM dateRange
GROUP BY ID, Grouping
ORDER BY ID
Heres the code:
http://sqlfiddle.com/#!3/f3615/1
I hope this helps!

Create a weekCount column in SQL Server 2012

I have this data:
id worked_date
-----------------
1 2013-09-25
2 2013-09-26
3 2013-10-01
4 2013-10-04
5 2013-10-07
I want to add a column called weekCount. The based date is 2013-09-25. So all the data with worked_date from 2013-09-25 to 2013-10-01 will have weekCount as 1 and from 2013-10-02 to 2013-10-8 will have weekCount as 2 and so on. How can that be done?
Thanks.
Here's one way using DATEDIFF:
select id,
worked_date,
1 + (datediff(day, '2013-09-25', worked_date) / 7) weekCount
from yourtable
SQL Fiddle Demo
Perhaps an approach like this will solve your problem.
I compute an in-memory table that contains the week's boundaries along with a monotonically increasing number (BuildWeeks). I then compare my worked_date values to my date boundaries. Based on your comment to #sgeddes, you need the reverse week number so I then use a DENSE_RANK function to calculate the ReverseWeekNumber.
WITH BOT(StartDate) AS
(
SELECT CAST('2013-09-25' AS date)
)
, BuildWeeks (WeekNumber, StartOfWeek, EndOfWeek) AS
(
SELECT
N.number AS WeekNumber
, DateAdd(week, N.number -1, B.StartDate) AS StartOfWeek
, DateAdd(d, -1, DateAdd(week, N.number, B.StartDate)) AS EndOfWeek
FROM
dbo.Numbers AS N
CROSS APPLY
BOT AS B
)
SELECT
M.*
, BW.*
, DENSE_RANK() OVER (ORDER BY BW.WeekNumber DESC) AS ReverseWeekNumber
FROM
dbo.MyTable M
INNER JOIN
BuildWeeks AS BW
ON M.worked_date BETWEEN BW.StartOfWeek ANd BW.EndOfWeek
;
SQLFiddle
If you are looking for a Fiscal Week number, I would use a function that would calculate the week:
CREATE FUNCTION FiscalWeek(#FiscalStartDate datetime, #EvalDate datetime)
RETURNS INT
AS
BEGIN
DECLARE #weekNumber INT = (DATEDIFF(DAY, #FiscalStartDate, #EvalDate) / 7) + 1
RETURN (#weekNumber % 52)
END
GO
If you used a fiscal starting date of '2013-09-25' and an evaluation date of '2014-09-25' you would get a week number of 1.
Using a function gives you a little more flexibility to do whatever you need.
Perhaps not the most elegant way but this works for me to get the top rank number:
WITH CTE AS (
SELECT employee_id, DENSE_RANK() OVER (ORDER BY DATEDIFF(DAY, ''20130925'', worked_date )/7 DESC) AS weekRank
FROM Timesheet
)
SELECT TOP (1) weekRank
FROM CTE
WHERE employee_id=#employee_id
ORDER BY weekRank DESC
This is how I can create weekRank column and pass a parameter dynamically:
WITH rank_cte AS (
SELECT timesheet_id,employee_id, date_worked,
dateadd(week, datediff(day,'20000105',worked_date) / 7, '20000105') AS WeekStart,
dateadd(week, datediff(day,'20000105',worked_date) / 7, '20000105')+6 AS WeekEnd,
DENSE_RANK() OVER (ORDER BY 1 + DATEDIFF(DAY, '20130925', worked_date )/7 DESC) AS weekRank
FROM Timesheet
)
SELECT timesheet_id, worked_date, WeekStart, WeekEnd, weekRank
FROM rank_cte rc
WHERE employee_id=#employee_id
AND weekRank=#weekRank
ORDER BY worked_date DESC
Thanks