I have an Event Calendar that I am trying to create in SQL.The issue is I can not pivot the events like I do the dates. Is there a way to do two pivots or something similiar so that the events line up under the dates.
Here is the table before a pivot
YearNum MonthName week_number DayNum DayofWeek EventStart EventSubject EventLocation EventDate
2018 August 33 14 Tuesday NULL NULL NULL NULL
2018 August 33 15 Wednesday NULL NULL NULL NULL
2018 August 33 16 Thursday 13:00 Dan's Birthday EC-E 2018-08-16
2018 August 33 17 Friday NULL NULL NULL NULL
2018 August 33 18 Saturday NULL NULL NULL NULL
2018 August 34 19 Sunday NULL NULL NULL NULL
2018 August 34 20 Monday NULL NULL NULL NULL
After the pivot, this is how it looks
Here is the SQL Query that I am currently working with:
SELECT MonthName, YearNum, Sunday, Monday,Tuesday,Wednesday,Thursday,Friday,Saturday, EventStart, EventSubject, EventLocation, EventDate
FROM
(
SELECT CalendarDates.YearNum, CalendarDates.MonthName, DATEPART(WEEK, CalendarDates.StandardDate) AS week_number, CalendarDates.DayNum,
CalendarDates.DayofWeek, EventList.EventStart, EventList.EventSubject, EventList.EventLocation, EventList.EventDate
FROM (SELECT EventStart, EventSubject, EventLocation, EventDate
FROM EventList AS EventList_1) AS EventList RIGHT OUTER JOIN
CalendarDates ON EventList.EventDate = CalendarDates.StandardDate
WHERE (CalendarDates.MonthNum = '8') AND (CalendarDates.YearNum = '2018')
)
pivotDates
PIVOT (MIN(DayNum) FOR DayofWeek IN (Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday)) AS Pivots
This is how the output should look like
This problem could be better solved in the presentation layer, as the SQL does a good job on computing/filtering data, but not in user interface.
Anyways, try this:
DECLARE #CurrentMonth INT = 8,
#CurrentYear INT = 2018
;WITH RawData AS -- Your original appointment dates
(
SELECT CAST('2018-08-16' AS DATE) Date,
CAST('13:00' AS TIME) Start,
'Someone birthday' Subject,
'Location' Location
), AllMonthDays AS -- list of all days in the filtered month
(
SELECT CAST(CAST(#CurrentYear AS VARCHAR) + '-' +
CAST(#CurrentMonth AS VARCHAR) + '-01' AS DATE) Date UNION ALL
SELECT DATEADD(D, +1, Date)
FROM AllMonthDays
WHERE MONTH(DATEADD(D, +1, Date)) = #CurrentMonth
), CalendarData AS -- Adds the weekday name, week numbering and appointment data
(
SELECT AllMonthDays.Date,
DATEPART(WEEK , AllMonthDays.Date) WeekNumber,
DATENAME(DW , AllMonthDays.Date) WeekdayName,
NULLIF(CONCAT(
CAST(RawData.Start AS CHAR(5)), ' ',
RawData.Subject, '# ', RawData.Location), ' # ') Appointment
FROM AllMonthDays
LEFT JOIN RawData
ON RawData.Date = AllMonthDays.Date
), CalendarFormat AS -- PIVOT the data, putting the weekdays as columns
(
SELECT *
FROM CalendarData
PIVOT
( --MIN(Date) FOR -- uncomment this to a better understanding
MIN(Appointment) FOR -- weird part: using MIN in a text column
WeekdayName IN
( [Sunday], [Monday], [Tuesday],
[Wednesday], [Thursday], [Friday], [Saturday]
)
) p
)
SELECT MIN(WeekNumber ) [WeekNumber],
MIN([Sunday] ) [Sunday],
MIN([Monday] ) [Monday],
MIN([Tuesday] ) [Tuesday],
MIN([Wednesday]) [Wednesday],
MIN([Thursday] ) [Thursday],
MIN([Friday] ) [Friday],
MIN([Saturday] ) [Saturday]
FROM CalendarFormat
GROUP BY WeekNumber -- suppress duplications
ORDER BY WeekNumber
Related
[EDITED TO SIMPLIFY]
I have 500+ records. All of which have a reference number, a start date, an end date and a total machining time.
Ref StartDate EndDate MachineTimeHours
123 24/01/2020 30/01/2020 28
321 25/02/2020 27/02/2020 18
Starting at the start date, I need to calculate how many machining hours fall into 1 week and how many fall into the next.
Our working days are Monday to Thursday 8 Hours & Friday 4 Hours.
Ref 321 has a start of 25/2 which is a Tuesday and a finish date of 27/2 which is a Thursday in the same week. This will calculate as all 18 hours being in the same week.
Ref 123 has a start of 24/01. This is a Friday in Week 4 of 2020.
Based on my rules, that would be 4 hours in week 4 and 24 Hours in week 5.
I have a table called 'DatesList' which has all days on it (as well as week number and working hours).
I need my table to list each record for each week and I'll do the grouping on a separate report.
In effect I'd like
Ref StartDate EndDate MachineTimeHours Week Hours
123 24/01/2020 30/01/2020 28 4 4
123 24/01/2020 30/01/2020 28 5 24
321 25/02/2020 27/02/2020 18 9 18
You can start with creating some reference tables.
For the example those are just temporary tables.
Reference data:
--
-- Reference tables
--
CREATE TABLE #ref_calendar
(
CalDate DATE PRIMARY KEY,
DayOfWeek SMALLINT NOT NULL,
WeekNr SMALLINT NOT NULL,
IsHoliday BIT NOT NULL DEFAULT 0
);
DECLARE #year int = 2020;
SET DATEFIRST 1; -- 1: monday start
;WITH RCTE_DATES AS
(
SELECT
DATEFROMPARTS(#year, 1, 1) AS caldate
UNION ALL
SELECT dateadd(day, 1, caldate)
FROM RCTE_DATES
WHERE caldate <= DATEFROMPARTS(#year, 12, 31)
)
INSERT INTO #ref_calendar (CalDate, DayOfWeek, WeekNr)
SELECT
caldate,
DATEPART(weekday, caldate) AS DayOfWeek,
DATEPART(week, caldate) AS WeekNr
FROM rcte_dates c
WHERE NOT EXISTS
(
SELECT 1
FROM #ref_calendar ref
WHERE ref.CalDate = c.caldate
)
OPTION (MAXRECURSION 366);
CREATE TABLE #ref_workhours
(
Id INT IDENTITY(1,1) PRIMARY KEY,
DayOfWeek SMALLINT NOT NULL,
WorkHours DECIMAL(4,2) NOT NULL,
ActiveFrom DATE NOT NULL DEFAULT GetDate(),
ActiveTill DATE
);
INSERT INTO #ref_workhours
(DayOfWeek, WorkHours) VALUES
(1, 8.0), (2, 8.0), (3, 8.0), (4, 8.0), (5, 4.0),
(6, 0), (7, 0);
Some sample data:
--
-- Sample data
--
CREATE TABLE YourDateRangeTable
(
Id INT IDENTITY(1,1) PRIMARY KEY,
JobNumber INT NOT NULL,
PartNumber VARCHAR(8) NOT NULL,
Machine CHAR(3) NOT NULL,
StartDate DATE NOT NULL,
EndDate DATE NOT NULL
);
INSERT INTO YourDateRangeTable
(JobNumber, PartNumber, Machine, StartDate, EndDate) values
(12345, 'XYZ321', 'DL8', '2020-01-24', '2020-01-30');
Then you can run a query that uses the reference tables.
SELECT JobNumber, PartNumber, Machine
, YEAR(cal.CalDate) AS [Year]
, cal.WeekNr AS [Week]
, SUM(w.WorkHours) AS [Hours]
FROM YourDateRangeTable t
JOIN #ref_calendar cal
ON cal.CalDate >= t.StartDate
AND cal.CalDate < t.EndDate
JOIN #ref_workhours w
ON w.DayOfWeek = cal.DayOfWeek
GROUP BY JobNumber, PartNumber, Machine
, YEAR(cal.CalDate), cal.WeekNr;
Returns:
JobNumber PartNumber Machine Year Week Hours
12345 XYZ321 DL8 2020 4 4.00
12345 XYZ321 DL8 2020 5 24.00
A test on db<>fiddle here
You can get all the detes of two given date and also weeknumber and a case statement for the working hour. Based on the result from the inner query write an outer query which will give the sum of total working hour.
Here is the given query.
DECLARE #MinDate DATE = '20200124',
#MaxDate DATE = '20200130'
--Fri Week 4 = 4 hours
--Mon Week 5 = 8 hours
--Tue Week 5 = 8 hours
--Wed Week 5 = 8 hours
Select WeekNo, SUM(WorkingHour) as TotalWorkingHour from(
Select [DATE], DATEPART(WEEK, [DATE]) - DATEPART(WEEK, DATEADD(MM, DATEDIFF(MM,0,[DATE]), 0))+ 1 as WeekNo,
DATENAME(weekday, [DATE]) as WeekDay, Case DATENAME(weekday, [DATE])
when 'Friday' then 4
when 'Monday' then 8
when 'Tuesday' then 8
when 'Wednesday' then 8
else 0
end as WorkingHour from(
SELECT TOP (DATEDIFF(DAY, #MinDate, #MaxDate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #MinDate)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
)a
)b group by WeekNo
It will give the result as below:
WeekNo TotalWorkingHour
------------------------
4 4
5 24
You can find the demo Here.
Code:
DECLARE #CurrentDate DATE = '2018-01-02' ; -- Can be any date. This is just a date I used as an anchor point.
DECLARE #BillingDayOfMonth INT = 31 ; -- The day of the billing every month (can be between 1st and 31st of the month).
;WITH [TT1] ( [N] ) AS
(
SELECT 1
UNION ALL
SELECT 1
)
, [TT2] ( [N] ) AS
(
SELECT 1
UNION ALL
SELECT 1
UNION ALL
SELECT 1
UNION ALL
SELECT 1
UNION ALL
SELECT 1
UNION ALL
SELECT 1
)
, [TT] ( [N] ) AS
(
SELECT ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL ) )
FROM [TT1] [T1]
CROSS JOIN [TT2] [T2]
)
SELECT [N]
FROM [TT] ; -- This gives me the count of 12 cycles (can vary).
Goal:
To return current and X number of previous billing cycle dates (12 cycles total in the above example).
If the BillingDayOfMonth doesn't exists in a month (i.e. 29th to 31st depending on the month), choose the last day of the month as the BillingDayOfMonth. So if BillingDayOfMonth = 31st, this day doesn't exist in Nov. So The Cycle will start #11/30 instead of 11/31. The above code is incomplete and need your help figuring out an efficient solution. Thank you
Desired Output:
CycleStartDate CycleEndDate
2017-12-31 2018-01-30 -- The most current cycle
2017-11-30 2017-12-30
2017-10-31 2017-11-29
2017-09-30 2017-10-30
2017-08-31 2017-09-29
2017-07-31 2017-08-30
2017-06-30 2017-07-30
2017-05-31 2017-06-29
2017-04-30 2017-05-30 -- Notice the EndDate
2017-03-31 2017-04-29 -- Notice the EndDate
2017-02-28 2017-03-30 -- Notice the StartDate
2017-01-31 2017-02-27 -- Notice the EndDate
So based on your numbers table approach and your rules, you can use a query like below.
I tested this for #BillingDayOfMonth values like 25, 28, 30 and found results reasonably true.
See live demo
DECLARE #CurrentDate DATE = '2018-01-02' ; -- Can be any date. This is just a date I used as an anchor point.
DECLARE #BillingDayOfMonth INT = 31 ; -- The day of the billing every month (can be between 1st and 31st of the month).
DECLARE #StartDate DATE
SET #StartDate= CASE WHEN
DAY(EOMONTH(#CurrentDate)) < #BillingDayOfMonth
THEN
EOMONTH(#CurrentDate)
ELSE
DATEFROMPARTS(YEAR(#CurrentDate),MONTH(#CurrentDate),#BillingDayOfMonth)
END
;WITH [TT1] ( [N] ) AS
(
SELECT 1
UNION ALL
SELECT 1
)
, [TT2] ( [N] ) AS
(
SELECT 1
UNION ALL
SELECT 1
UNION ALL
SELECT 1
UNION ALL
SELECT 1
UNION ALL
SELECT 1
UNION ALL
SELECT 1
)
, [TT] ( [N] ) AS
(
SELECT
ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL ) )
FROM [TT1] [T1]
CROSS JOIN [TT2] [T2]
)
SELECT CycleStartDate =
DATEADD(M,-N,#StartDate)
,
CycleEndDate =
DATEADD(D,-1,DATEADD(M,1-N,#StartDate))
FROM [TT] ;
SELECT cast(Ord.regdate AS date) AS Date,
COUNT(Ord.Ordernumber) AS [Backlog of line],
(SELECT CASE
WHEN CAST(SUM(Ord.Qty) AS INT) IS NULL THEN 0
ELSE CAST(SUM(Ord.Qty) AS INT)
END) AS [Backlog of Qty]
FROM Orders [Ord]
WHERE Ord.regdate < CAST(CAST(CAST(DATEADD(DAY,-1,GETDATE()) AS date) AS varchar(10)) + ' 12:00' AS datetime)
GROUP BY cast(Ord.regdate AS date)
ORDER BY cast(Ord.regdate AS date) DESC
Date : Backlog of line : Backlog of Qty
2015-09-20 : 10 : 50
2015-09-21 : 5 : 25
The problem here is that when we get to next day... the 21 will get more records if the are orders that are done after 12:00 the 2015-09-21...
When we run this it just takes the records that are older then 12:00 the day before and groups them by date.
My Question is..
I want the date 2015-09-22 to show and have the records after 12:00 from 2015-09-21 until 12:00 the 2015-09-22... and so an
This should works. It shifts regdate by 12 hours first.
SELECT Ord.regdate AS Date,
COUNT(Ord.Ordernumber) AS [Backlog of line],
(SELECT CASE
WHEN CAST(SUM(Ord.Qty) AS INT) IS NULL THEN 0
ELSE CAST(SUM(Ord.Qty) AS INT)
END) AS [Backlog of Qty]
FROM (
Select Ordernumber, Qty, CAST(DATEADD(HOUR, 12, regdate) as date) as regdate
From Orders
WHERE regdate < CAST(CAST(CAST(DATEADD(DAY,-1,GETDATE()) AS date) AS varchar(10)) + ' 12:00' AS datetime)
) [Ord]
GROUP BY Ord.regdate
ORDER BY Ord.regdate DESC
I need to create a temp table or common table expression based on 2 paremters in a SQL Server 2012 environment
#calYear
#currentYear
so if #calYear = 5 and #currentYear='2014'
I would like to generate a temp table of 5 years starting from current year with 4 columns like
YearDesc MonthName MonthNum FirstDayOfMonth
2014 Jan 1 1/1/2014
2014 Feb 2 2/1/2014
2014 Mar 3 3/1/2014
...
...
...
2018 Oct 10 10/1/2018
2018 Nov 11 11/1/2018
2018 Dec 12 12/1/2018
Is it possible to do a Do While loop efficently? How would I account for the month names?
I'm using a really cumbersome Do While loop to iterate all the months of the year then iterate all the years.
One way using a recursive cte:
declare #calYear int = 5, #currentYear char(4) = '2014'
;with cte (dt) as (
select DATEFROMPARTS(#currentyear,1,1) dt
union all
select dateadd(month,1,dt)
from cte where dt < dateadd(year,#calyear,DATEFROMPARTS(#currentyear,1,1))
)
select year(dt) YearDesc, datename(month, dt) MonthName, month(dt) MonthNum, dt FirstDayOfMonth
from cte
order by dt
Or using a numbers table: (in this case master..spt_values)
declare #calYear int = 5, #currentYear char(4) = '2014'
;with cte2 (dt) as (
select dateadd(month,number,DATEFROMPARTS(#currentyear,1,1)) dt
from master..spt_values where type = 'p'
and number <= 12*#calYear
)
select year(dt) YearDesc, datename(month, dt) MonthName, month(dt) MonthNum, dt FirstDayOfMonth
from cte2
order by dt
I have a membership table with the following columns:
Member_number | StartDate | EndDate
XYZ | 01-Jan-2002 | 01-March-2002
ABC | 01-Feb-2002 | 01-March-2002
Basically, I want to show how many members were present in specific month. My problem is I don't know how to break this time span into months. How can I see this result?
Month | NumberOfMembers
Jan | 1
Feb | 2
March | 2
Given a members table that looks something like this:
create table dbo.members
(
member_number int not null primary key ,
start_date datetime not null ,
end_date datetime not null ,
)
And a table-valued function that generates sequences of consecutive integers, like this:
create function dbo.IntegerRange ( #from int , #thru int )
returns #sequence table
(
value int not null primary key clustered
)
as
begin
declare #increment int = case when #from > #thru then -1 else 1 end ;
with sequence(value) as
(
select value = #from
union all
select value + #increment
from sequence
where value < #thru
)
insert #sequence
select value
from sequence
order by value
option ( MAXRECURSION 0 )
return
end
A query like this should give you what you want:
select [year] = period.yyyy ,
[month] = case period.mm ,
when 1 then 'Jan'
when 2 then 'Feb'
when 3 then 'Mar'
when 4 then 'Apr'
when 5 then 'May'
when 6 then 'Jun'
when 7 then 'Jul'
when 8 then 'Aug'
when 9 then 'Sep'
when 10 then 'Oct'
when 11 then 'Nov'
when 12 then 'Dev'
else '***'
end ,
member_cnt = sum( case when m.member_number is not null then 1 else 0 end )
from ( select yyyy = yyyy.value ,
mm = mm.value ,
dtFrom = dateadd( month , mm.value - 1 , dateadd( year , yyyy.value - 1900 , convert(date,'') ) ) ,
dtThru = dateadd( day , - 1 , dateadd( month , mm.value , dateadd( year , yyyy.value - 1900 , convert(date,'') ) ) )
from dbo.IntegerRange(2000,2013) yyyy
full join dbo.IntegerRange(1,12) mm on 1=1
) period
left join dbo.members m on period.dtFrom <= m.end_date
and period.dtThru >= m.start_date
group by period.yyyy ,
period.mm
order by period.yyyy ,
period.mm
The first table expression in the from clause creates a virtual table of the periods (months, in this case, but the technique doesn't limit itself to months or even weeks) covering the reporting period:
from ( select yyyy = yyyy.value ,
mm = mm.value ,
dtFrom = dateadd( month , mm.value - 1 , dateadd( year , yyyy.value - 1900 , convert(date,'') ) ) ,
dtThru = dateadd( day , - 1 , dateadd( month , mm.value , dateadd( year , yyyy.value - 1900 , convert(date,'') ) ) )
from dbo.IntegerRange(2000,2013) yyyy
full join dbo.IntegerRange(1,12) mm on 1=1
) period
That is then joined, via a left outer join, ensuring that all periods are reported, not just those periods with active members, to the members table to collect, for each reporting period in the virtual period table above, the set of members who were active during the period:
left join dbo.members m on period.dtFrom <= m.end_date
and period.dtThru >= m.start_date
We then group by the year and month of each period and then order the results by year/month number:
group by period.yyyy ,
period.mm
order by period.yyyy ,
period.mm
In creating the results set to be returned, we return the year of the period, the month number (converted to a friendly name), and the count of active members. Note that we have to use the sum() aggregate function here rather than count() as empty periods will have a single row returned (with null in all columns). Count(), unlike all other aggregate functions, includes null values in the aggregation. Sum() is applied to a case expression acting as a discriminant function returning 1 or 0 identifying whether the row indicates useful or missing data:
select [year] = period.yyyy ,
[month] = case period.mm ,
when 1 then 'Jan'
when 2 then 'Feb'
when 3 then 'Mar'
when 4 then 'Apr'
when 5 then 'May'
when 6 then 'Jun'
when 7 then 'Jul'
when 8 then 'Aug'
when 9 then 'Sep'
when 10 then 'Oct'
when 11 then 'Nov'
when 12 then 'Dev'
else '***'
end ,
member_cnt = sum( case when m.member_number is not null then 1 else 0 end )
Easy!
DECLARE #minMonth DATE
SELECT #minMonth = MIN(StartDate) FROM Table1
DECLARE #maxMonth DATE
SELECT #maxMonth = MAX(EndDate) FROM Table1
;WITH CTE_Months AS
(
SELECT #minMonth AS Mnth
UNION ALL
SELECT DATEADD(MM,1,Mnth) FROM CTE_Months
WHERE Mnth<#MaxMonth
)
SELECT Mnth AS Month, COUNT(*) as Members
FROM CTE_Months m
LEFT JOIN Table1 t on m.Mnth BETWEEN t.StartDate AND t.EndDate
GROUP BY Mnth
SQLFiddle Demo
CTE will find all months from min StartDate to max EndDate, if you need different min and max, just change how you get #MinMonth and #MaxMonth
If you don't want to show zeros for months that possibly don't have any members, replace LEFT JOIN with INNER at the end.
select t.Month, count(*) Members
from (
select case
when startdate <= to_date('01-Jan-2002', 'DD-MON-YYYY') AND enddate >= to_date('31-Jan-2002', 'DD-MON-YYYY') then ' Jan'
when startdate <= to_date('01-Feb-2002', 'DD-MON-YYYY') AND enddate >= to_date('28-Feb-2002', 'DD-MON-YYYY') then ' Feb'
when startdate <= to_date('01-Mar-2002', 'DD-MON-YYYY') AND enddate >= to_date('31-Mar-2002', 'DD-MON-YYYY') then ' Mar'
when startdate <= to_date('01-Apr-2002', 'DD-MON-YYYY') AND enddate >= to_date('30-Apr-2002', 'DD-MON-YYYY') then ' Apr'
when startdate <= to_date('01-May-2002', 'DD-MON-YYYY') AND enddate >= to_date('31-May-2002', 'DD-MON-YYYY') then ' May'
when startdate <= to_date('01-Jun-2002', 'DD-MON-YYYY') AND enddate >= to_date('30-Jun-2002', 'DD-MON-YYYY') then ' Jun'
when startdate <= to_date('01-Jul-2002', 'DD-MON-YYYY') AND enddate >= to_date('31-Jul-2002', 'DD-MON-YYYY') then ' Jul'
when startdate <= to_date('01-Aug-2002', 'DD-MON-YYYY') AND enddate >= to_date('31-Aug-2002', 'DD-MON-YYYY') then ' Aug'
when startdate <= to_date('01-Sep-2002', 'DD-MON-YYYY') AND enddate >= to_date('30-Sep-2002', 'DD-MON-YYYY') then ' Sep'
when startdate <= to_date('01-Oct-2002', 'DD-MON-YYYY') AND enddate >= to_date('31-Oct-2002', 'DD-MON-YYYY') then ' Oct'
when startdate <= to_date('01-Nov-2002', 'DD-MON-YYYY') AND enddate >= to_date('30-Nov-2002', 'DD-MON-YYYY') then ' Nov'
when startdate <= to_date('01-Dec-2002', 'DD-MON-YYYY') AND enddate >= to_date('31-Dec-2002', 'DD-MON-YYYY') then ' Dec'
end as Month
from member) t
group by t.Month
Where member is the table name.