I am trying to split a time frequency that has a start time, an end time, a frequency and a duration into separate rows. Here is some example data:
+------+------------+----------+-----------------+---------------+
| Name | Start_Time | End_Time | Frequency_Hours | Duration_Mins |
+------+------------+----------+-----------------+---------------+
| A | 08:00:00 | 18:00:00 | 2 | 2 |
| B | 00:00:00 | 23:59:59 | 1 | 5 |
| C | 00:00:00 | 23:59:59 | 4 | 15 |
+------+------------+----------+-----------------+---------------+
Can be created using the following query:
DECLARE #Tmp AS TABLE(Name VARCHAR(128)
,Start_Time VARCHAR(8)
,End_Time VARCHAR(8)
,Frequency_Hours INT
,Duration_Mins INT)
INSERT INTO #Tmp VALUES ('A','08:00:00', '18:00:00', 2,2)
,('B','00:00:00', '23:59:59', 1,5)
,('C','00:00:00', '23:59:59', 4,15)
Here is my desired output (I will then use this to drive a gantt chart visualisation):
+------+------------+----------+
| Name | Start_Time | End_Time |
+------+------------+----------+
| A | 08:00:00 | 08:02:00 |
| A | 10:00:00 | 10:02:00 |
| A | 12:00:00 | 12:02:00 |
| A | 14:00:00 | 14:02:00 |
| A | 16:00:00 | 16:02:00 |
| A | 18:00:00 | 18:02:00 |
| B | 00:00:00 | 00:05:00 |
| B | 01:00:00 | 01:05:00 |
| B | 02:00:00 | 02:05:00 |
| B | 03:00:00 | 03:05:00 |
| B | 04:00:00 | 04:05:00 |
| B | 05:00:00 | 05:05:00 |
| B | 06:00:00 | 06:05:00 |
| B | 07:00:00 | 07:05:00 |
| B | 08:00:00 | 08:05:00 |
| B | 09:00:00 | 09:05:00 |
| B | 10:00:00 | 10:05:00 |
| B | 11:00:00 | 11:05:00 |
| B | 12:00:00 | 12:05:00 |
| B | 13:00:00 | 13:05:00 |
| B | 14:00:00 | 14:05:00 |
| B | 15:00:00 | 15:05:00 |
| B | 16:00:00 | 16:05:00 |
| B | 17:00:00 | 17:05:00 |
| B | 18:00:00 | 18:05:00 |
| B | 19:00:00 | 19:05:00 |
| B | 20:00:00 | 20:05:00 |
| B | 21:00:00 | 21:05:00 |
| B | 22:00:00 | 22:05:00 |
| B | 23:00:00 | 23:05:00 |
| C | 00:00:00 | 00:15:00 |
| C | 04:00:00 | 04:15:00 |
| C | 08:00:00 | 08:15:00 |
| C | 12:00:00 | 12:15:00 |
| C | 16:00:00 | 16:15:00 |
| C | 20:00:00 | 20:15:00 |
+------+------------+----------+
I am hoping to be able to create a view out of this so I am trying to do it without cursors or other cpu intensive methods.
Any ideas?
Thanks,
Dan.
You could use a recursive cte like this
;WITH temp AS
(
SELECT t.Name, CAST(t.Start_Time AS time) AS CurrentStart_Time, dateadd(minute,t.Duration_Mins,CAST(t.Start_Time AS time)) AS CurrentEnd_Time, t.Frequency_Hours, CAST(t.End_Time AS time) AS End_Time
FROM #Tmp t
UNION ALL
SELECT t.Name, dateadd(hour,t.Frequency_Hours,t.CurrentStart_Time), dateadd(hour,t.Frequency_Hours,t.CurrentEnd_Time), t.Frequency_Hours, t.End_Time
FROM temp t
WHERE t.CurrentStart_Time < t.End_Time AND t.CurrentStart_Time < dateadd(hour,t.Frequency_Hours,t.CurrentStart_Time)
)
SELECT t.Name, t.CurrentStart_Time, t.CurrentEnd_Time
FROM temp t
ORDER BY t.Name
OPTION (MAXRECURSION 0)
Demo link: http://rextester.com/XJK25805
It can be done without RECURSIIVE CTE also.
If we create number instead of using
select distinct number master..spt_values then performance will be far better.
Like Number table can be populated from 1 to 100.
try this with various sample data,
declare #t table(Name varchar(20), Start_Time time(0),End_Time time(0)
, Frequency_Hours int,Duration_Mins int)
insert into #t VALUES
('A','08:00:00','18:00:00', 2 , 2 )
,('B','00:00:00','23:59:59', 1 , 5 )
,('C','00:00:00','23:59:59', 4 ,15 )
SELECT NAME
,dateadd(hour, n, Start_Time) Start_Time
,dateadd(minute, Duration_Mins, (dateadd(hour, n, Start_Time))) End_Time
FROM #t t
CROSS APPLY (
SELECT DISTINCT number * Frequency_Hours n
FROM master..spt_values
WHERE number >= 0
AND number <= datediff(HOUR, t.Start_Time, t.End_Time) / Frequency_Hours
) ca
I need to get start and end date for each week in given month/year. (month and year are always given - like march 2017).
Example, january 2017:
1 week : '2017-01-01' - '2017-01-01'
2 week: '2017-01-02' - '2017-01-08'
3 week: '2017-01-09' - '2017-01-15'
4 week: '2017-01-16' - '2017-01-22'
5 week: '2017-01-23' - '2017-01-29'
6 week: '2017-01-30' - '2017-01-31'
I already know how to get number of weeks for given month / year:
select *,
DATEDIFF(WEEK, DATEADD(day,-1,StartAt), DATEADD(day,-1,EndAt)) +1
as NumWeeks
But how to get start / end date for each week for the given month/year?
If you just want a function to return the weeks of a month for one month then this would do what you want:
create function dbo.udf_weeks_of_month (#fromdate date)
returns table as return (
with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, dates as (
select top (datediff(day, #fromdate, dateadd(month, datediff(month, 0, #fromdate )+1, 0)))
[DateValue]=convert(date,dateadd(day,row_number() over(order by (select 1))-1,#fromdate))
from n as deka cross join n as hecto
)
select
WeekOfMonth = row_number() over (order by datepart(week,DateValue))
, Week = datepart(week,DateValue)
, WeekStart = min(DateValue)
, WeekEnd = max(DateValue)
from dates
group by datepart(week,DateValue)
);
and calling it like so:
set datefirst 1;
select * from dbo.udf_weeks_of_month('20170101');
returns:
+-------------+------+------------+------------+
| WeekOfMonth | Week | WeekStart | WeekEnd |
+-------------+------+------------+------------+
| 1 | 1 | 2017-01-01 | 2017-01-01 |
| 2 | 2 | 2017-01-02 | 2017-01-08 |
| 3 | 3 | 2017-01-09 | 2017-01-15 |
| 4 | 4 | 2017-01-16 | 2017-01-22 |
| 5 | 5 | 2017-01-23 | 2017-01-29 |
| 6 | 6 | 2017-01-30 | 2017-01-31 |
+-------------+------+------------+------------+
and this call:
select * from dbo.udf_weeks_of_month('february 2017');
returns:
+-------------+------+------------+------------+
| WeekOfMonth | Week | WeekStart | WeekEnd |
+-------------+------+------------+------------+
| 1 | 6 | 2017-02-01 | 2017-02-05 |
| 2 | 7 | 2017-02-06 | 2017-02-12 |
| 3 | 8 | 2017-02-13 | 2017-02-19 |
| 4 | 9 | 2017-02-20 | 2017-02-26 |
| 5 | 10 | 2017-02-27 | 2017-02-28 |
+-------------+------+------------+------------+
rextester demo: http://rextester.com/VKPQU7936 (note: rextester reformats the dates)
Something like this?
DECLARE #Dates TABLE (DateId INT IDENTITY, Dt Date);
DECLARE #STart Date = DATEADD(Year, DATEDIFF(Year, 0, GETDATE()),0)
SET NOCOUNT ON;
WHILE #STart <= DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()), 0)
BEGIN
INSERT INTO #Dates (Dt) VALUES (#STart)
SELECT #Start = DATEADD(DAY, 1, #STart)
END
Updated
SELECT
DateId
, Dt
, DATEADD(WEEK, DATEDIFF(DAY, 0, Dt)/7, 0) AS WeekBeginningMondayOf
, DATEADD(DAY, 6, DATEADD(WEEK, DATEDIFF(DAY, 0, Dt)/7, 0)) AS WeekEndingSundayOf
, DENSE_RANK() OVER(PARTITION BY MONTH(Dt) ORDER BY DATEADD(WEEK, DATEDIFF(DAY, 0, Dt)/7, 0)) AS WeekInMonth
FROM #Dates
It is very smart in such situations to use a numbers / date / tally table. Such a table is very handsome in many situations!
In this answer I show an approach how to create and fill such a table.
Once you have created this table, the query is as simple as:
SELECT n.CalendarDate AS StartOfWeek
,DATEADD(DAY,6,n.CalendarDate) AS EndOfWeek
FROM dbo.RunningNumbers AS n
WHERE n.CalendarDate>={d'2017-01-01'} AND n.CalendarDate<{d'2017-02-01'}
AND n.CalendarWeekDay=1;
hint Such a table can easily be extended with information hardly to compute simply by adding some values into extra columns manually (e.g. holydays, free-of-work days...)
So many variations on this theme. This one is a little shorter than other answers though.
declare #dt date = {fn current_date()};
declare #start_of_year date = datefromparts(year(#dt), 1, 1);
with digits(d) as (
select 0 union all select 1 union all select 2 union all select 3 union all
select 4 union all select 5 union all select 6 union all select 7
), wks as (
select
dateadd(week, d1.d * 8 + d0.d, dateadd(day, 1-datepart(weekday, #start_of_year), #start_of_year)) as week_start,
d1.d * 8 + d0.d as wk
from digits as d0 cross join digits as d1
)
select
case when year(week_start) < year(#dt)
then #start_of_year else week_start end as week_start,
case when year(dateadd(day, 6, week_start)) > year(#dt)
then datefromparts(year(#dt), 12, 31) else dateadd(day, 6, week_start) end as week_end
from wks
where wk between 0 and 53 and year(week_start) = year(#dt)
order by week_start;
I think what you are looking for is here ->someone asked on stackoverflow is similar to what you are looking for it.
This is the final version, hope as per your expectations
I build on the above link hope this is what you are looking for ( Apologies new to stackoverflow, unformatted t-sql below)
DECLARE #sDate DATETIME,
#eDate DATETIME
SET #sDate = '2017-03-01'
SET #eDate = DATEADD(DAY,-1, CAST(Cast(DatePart(YEAR,#sdate) AS varchar(4))
+'-' + Cast((DatePart(MONTH,#sdate)+1) AS varchar(2))+ '-1' AS Date))
SET #sDate = CAST(Cast(DatePart(YEAR,#sdate) AS varchar(4)) +'-'
+ Cast(DatePart(MONTH,#sdate) AS varchar(2))+ '-1' AS Date)
DECLARE #startDayOfWeekOffSet int ;
SET #startDayOfWeekOffSet=
(SELECT DATEPART(DW,#sDate));
DECLARE #DaysToGetFirstSaturday int
SET #DaysToGetFirstSaturday= 7- #startDayOfWeekOffSet
DECLARE #firstEverStartOfWeekDay Date , #firstWeekEndDay Date
SET #firstEverStartOfWeekDay =#sDate;
IF #startDayOfWeekOffSet = 1 -- January
BEGIN
SET #firstEverStartOfWeekDay = #sDate
SET #DaysToGetFirstSaturday = 0
SET #firstWeekEndDay = #sDate END
ELSE BEGIN IF DATEPART(MONTH,#sDate) > DATEPART(MONTH,#firstEverStartOfWeekDay)
AND #startDayOfWeekOffSet = 7 --FirstSundayNewMonth
BEGIN
SET #firstEverStartOfWeekDay =#sDate
SET #firstWeekEndDay = #sDate END ELSE --NotASundayNewMonth
BEGIN
SET #firstWeekEndDay = DATEADD(DAY,#DaysToGetFirstSaturday,#firstEverStartOfWeekDay);
END END
SELECT #firstWeekEndDay AS firstWeekEndDay,
#firstEverStartOfWeekDay AS firstEverStartOfWeekDay,
#DaysToGetFirstSaturday AS DaysToGetFirstSaturday,
#startDayOfWeekOffSet AS startDayOfWeekOffSet ;
WITH cte AS
(SELECT 1 AS WeekNum,
#firstEverStartOfWeekDay StartDate,
#firstWeekEndDay EndDate
UNION ALL SELECT WeekNum + 1, --Case when #scenario = 'NewYear' Then
dateadd(DAY, 1, cte.EndDate) --Else dateadd(ww, 1, cte.StartDate) End as
StartDate,
CASE
WHEN StartDate = EndDate THEN DateAdd(DAY,-1,dateadd(DAY, 8, cte.EndDate))
ELSE dateadd(ww, 1, cte.EndDate)
END AS EndDate
FROM cte
WHERE dateadd(ww, 1, StartDate)<= #eDate )
SELECT WeekNum,
CASE
WHEN DatePart(YEAR, StartDate) < DATEPART(YEAR,#sDate)
THEN CAST(Cast(DatePart(YEAR,#sdate) AS varchar(4)) + '-1-1' AS Date)
WHEN DatePart(MONTH, StartDate) < DATEPART(MONTH,#sDate)
THEN CAST(Cast(DatePart(YEAR,#sdate) AS varchar(4)) + '-'
+ Cast(DatePart(MONTH,#sdate) AS varchar(2)) + '-1' AS Date)
ELSE StartDate
END AS StartDate,
CASE
WHEN DatePart(MONTH, EndDate) > DATEPART(MONTH,#eDate) THEN #eDate
ELSE EndDate
END AS EndDate
FROM cte
select
DateValue
, WeekStart =convert(date,(
case when datepart(week,DateValue) =1
then convert(date, (datename(year,DateValue) +'0101'))
else dateadd(day,##datefirst-datepart(weekday,DateValue)-(##datefirst-1),DateValue)
end) )
, WeekEnd =convert(date,(
case when datepart(week,DateValue) =53
then convert(date, (datename(year,DateValue) +'1231'))
else dateadd(day,(##datefirst)-datepart(weekday,DateValue)+(7-##datefirst),DateValue)
end) )
from dates
rextester demo: http://rextester.com/KYKS44588
test setup:
set datefirst 1;
declare #fromdate date = '20161227', #thrudate date = '20201231';
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, dates as (
select top (datediff(day, #fromdate, #thrudate)+1)
[DateValue]=convert(date,dateadd(day,row_number() over(order by (select 1))-1,#fromdate))
from n as deka cross join n as hecto cross join n as kilo cross join n as [tenK]
order by 1
)
, test as (
select
convert(varchar(10),DateValue,120) as Date
, WeekStart = convert(varchar(10),convert(date,(
case when datepart(week,DateValue) =1
then convert(date, (datename(year,DateValue) +'0101'))
else dateadd(day,##datefirst-datepart(WeekDay,DateValue)-(##datefirst-1),DateValue)
end) ),120)
, WeekEnd = convert(varchar(10),convert(date,(
case when datepart(week,DateValue) =53
then convert(date, (datename(year,DateValue) +'1231'))
else dateadd(day,(##datefirst)-datepart(WeekDay,DateValue)+(7-##datefirst),DateValue)
end) ),120)
, week= datepart(week,DateValue)
from dates
)
select *
, dayname= datename(weekday,date)
, weekstartdayname= datename(weekday,weekstart)
, weekenddayname= datename(weekday,weekend)
from test
where week > 51
or week < 3
order by 1
results:
+------------+------------+------------+------+-----------+------------------+----------------+
| Date | WeekStart | WeekEnd | week | dayname | weekstartdayname | weekenddayname |
+------------+------------+------------+------+-----------+------------------+----------------+
| 2016-12-27 | 2016-12-26 | 2016-12-31 | 53 | Tuesday | Monday | Saturday |
| 2016-12-28 | 2016-12-26 | 2016-12-31 | 53 | Wednesday | Monday | Saturday |
| 2016-12-29 | 2016-12-26 | 2016-12-31 | 53 | Thursday | Monday | Saturday |
| 2016-12-30 | 2016-12-26 | 2016-12-31 | 53 | Friday | Monday | Saturday |
| 2016-12-31 | 2016-12-26 | 2016-12-31 | 53 | Saturday | Monday | Saturday |
| 2017-01-01 | 2017-01-01 | 2017-01-01 | 1 | Sunday | Sunday | Sunday |
| 2017-01-02 | 2017-01-02 | 2017-01-08 | 2 | Monday | Monday | Sunday |
| 2017-01-03 | 2017-01-02 | 2017-01-08 | 2 | Tuesday | Monday | Sunday |
| 2017-01-04 | 2017-01-02 | 2017-01-08 | 2 | Wednesday | Monday | Sunday |
| 2017-01-05 | 2017-01-02 | 2017-01-08 | 2 | Thursday | Monday | Sunday |
| 2017-01-06 | 2017-01-02 | 2017-01-08 | 2 | Friday | Monday | Sunday |
| 2017-01-07 | 2017-01-02 | 2017-01-08 | 2 | Saturday | Monday | Sunday |
| 2017-01-08 | 2017-01-02 | 2017-01-08 | 2 | Sunday | Monday | Sunday |
| 2017-12-18 | 2017-12-18 | 2017-12-24 | 52 | Monday | Monday | Sunday |
| 2017-12-19 | 2017-12-18 | 2017-12-24 | 52 | Tuesday | Monday | Sunday |
| 2017-12-20 | 2017-12-18 | 2017-12-24 | 52 | Wednesday | Monday | Sunday |
| 2017-12-21 | 2017-12-18 | 2017-12-24 | 52 | Thursday | Monday | Sunday |
| 2017-12-22 | 2017-12-18 | 2017-12-24 | 52 | Friday | Monday | Sunday |
| 2017-12-23 | 2017-12-18 | 2017-12-24 | 52 | Saturday | Monday | Sunday |
| 2017-12-24 | 2017-12-18 | 2017-12-24 | 52 | Sunday | Monday | Sunday |
| 2017-12-25 | 2017-12-25 | 2017-12-31 | 53 | Monday | Monday | Sunday |
| 2017-12-26 | 2017-12-25 | 2017-12-31 | 53 | Tuesday | Monday | Sunday |
| 2017-12-27 | 2017-12-25 | 2017-12-31 | 53 | Wednesday | Monday | Sunday |
| 2017-12-28 | 2017-12-25 | 2017-12-31 | 53 | Thursday | Monday | Sunday |
| 2017-12-29 | 2017-12-25 | 2017-12-31 | 53 | Friday | Monday | Sunday |
| 2017-12-30 | 2017-12-25 | 2017-12-31 | 53 | Saturday | Monday | Sunday |
| 2017-12-31 | 2017-12-25 | 2017-12-31 | 53 | Sunday | Monday | Sunday |
| 2018-01-01 | 2018-01-01 | 2018-01-07 | 1 | Monday | Monday | Sunday |
| 2018-01-02 | 2018-01-01 | 2018-01-07 | 1 | Tuesday | Monday | Sunday |
| 2018-01-03 | 2018-01-01 | 2018-01-07 | 1 | Wednesday | Monday | Sunday |
| 2018-01-04 | 2018-01-01 | 2018-01-07 | 1 | Thursday | Monday | Sunday |
| 2018-01-05 | 2018-01-01 | 2018-01-07 | 1 | Friday | Monday | Sunday |
| 2018-01-06 | 2018-01-01 | 2018-01-07 | 1 | Saturday | Monday | Sunday |
| 2018-01-07 | 2018-01-01 | 2018-01-07 | 1 | Sunday | Monday | Sunday |
| 2018-01-08 | 2018-01-08 | 2018-01-14 | 2 | Monday | Monday | Sunday |
| 2018-01-09 | 2018-01-08 | 2018-01-14 | 2 | Tuesday | Monday | Sunday |
| 2018-01-10 | 2018-01-08 | 2018-01-14 | 2 | Wednesday | Monday | Sunday |
| 2018-01-11 | 2018-01-08 | 2018-01-14 | 2 | Thursday | Monday | Sunday |
| 2018-01-12 | 2018-01-08 | 2018-01-14 | 2 | Friday | Monday | Sunday |
| 2018-01-13 | 2018-01-08 | 2018-01-14 | 2 | Saturday | Monday | Sunday |
| 2018-01-14 | 2018-01-08 | 2018-01-14 | 2 | Sunday | Monday | Sunday |
| 2018-12-24 | 2018-12-24 | 2018-12-30 | 52 | Monday | Monday | Sunday |
| 2018-12-25 | 2018-12-24 | 2018-12-30 | 52 | Tuesday | Monday | Sunday |
| 2018-12-26 | 2018-12-24 | 2018-12-30 | 52 | Wednesday | Monday | Sunday |
| 2018-12-27 | 2018-12-24 | 2018-12-30 | 52 | Thursday | Monday | Sunday |
| 2018-12-28 | 2018-12-24 | 2018-12-30 | 52 | Friday | Monday | Sunday |
| 2018-12-29 | 2018-12-24 | 2018-12-30 | 52 | Saturday | Monday | Sunday |
| 2018-12-30 | 2018-12-24 | 2018-12-30 | 52 | Sunday | Monday | Sunday |
| 2018-12-31 | 2018-12-31 | 2018-12-31 | 53 | Monday | Monday | Monday |
| 2019-01-01 | 2019-01-01 | 2019-01-06 | 1 | Tuesday | Tuesday | Sunday |
| 2019-01-02 | 2019-01-01 | 2019-01-06 | 1 | Wednesday | Tuesday | Sunday |
| 2019-01-03 | 2019-01-01 | 2019-01-06 | 1 | Thursday | Tuesday | Sunday |
| 2019-01-04 | 2019-01-01 | 2019-01-06 | 1 | Friday | Tuesday | Sunday |
| 2019-01-05 | 2019-01-01 | 2019-01-06 | 1 | Saturday | Tuesday | Sunday |
| 2019-01-06 | 2019-01-01 | 2019-01-06 | 1 | Sunday | Tuesday | Sunday |
| 2019-01-07 | 2019-01-07 | 2019-01-13 | 2 | Monday | Monday | Sunday |
| 2019-01-08 | 2019-01-07 | 2019-01-13 | 2 | Tuesday | Monday | Sunday |
| 2019-01-09 | 2019-01-07 | 2019-01-13 | 2 | Wednesday | Monday | Sunday |
| 2019-01-10 | 2019-01-07 | 2019-01-13 | 2 | Thursday | Monday | Sunday |
| 2019-01-11 | 2019-01-07 | 2019-01-13 | 2 | Friday | Monday | Sunday |
| 2019-01-12 | 2019-01-07 | 2019-01-13 | 2 | Saturday | Monday | Sunday |
| 2019-01-13 | 2019-01-07 | 2019-01-13 | 2 | Sunday | Monday | Sunday |
| 2019-12-23 | 2019-12-23 | 2019-12-29 | 52 | Monday | Monday | Sunday |
| 2019-12-24 | 2019-12-23 | 2019-12-29 | 52 | Tuesday | Monday | Sunday |
| 2019-12-25 | 2019-12-23 | 2019-12-29 | 52 | Wednesday | Monday | Sunday |
| 2019-12-26 | 2019-12-23 | 2019-12-29 | 52 | Thursday | Monday | Sunday |
| 2019-12-27 | 2019-12-23 | 2019-12-29 | 52 | Friday | Monday | Sunday |
| 2019-12-28 | 2019-12-23 | 2019-12-29 | 52 | Saturday | Monday | Sunday |
| 2019-12-29 | 2019-12-23 | 2019-12-29 | 52 | Sunday | Monday | Sunday |
| 2019-12-30 | 2019-12-30 | 2019-12-31 | 53 | Monday | Monday | Tuesday |
| 2019-12-31 | 2019-12-30 | 2019-12-31 | 53 | Tuesday | Monday | Tuesday |
| 2020-01-01 | 2020-01-01 | 2020-01-05 | 1 | Wednesday | Wednesday | Sunday |
| 2020-01-02 | 2020-01-01 | 2020-01-05 | 1 | Thursday | Wednesday | Sunday |
| 2020-01-03 | 2020-01-01 | 2020-01-05 | 1 | Friday | Wednesday | Sunday |
| 2020-01-04 | 2020-01-01 | 2020-01-05 | 1 | Saturday | Wednesday | Sunday |
| 2020-01-05 | 2020-01-01 | 2020-01-05 | 1 | Sunday | Wednesday | Sunday |
| 2020-01-06 | 2020-01-06 | 2020-01-12 | 2 | Monday | Monday | Sunday |
| 2020-01-07 | 2020-01-06 | 2020-01-12 | 2 | Tuesday | Monday | Sunday |
| 2020-01-08 | 2020-01-06 | 2020-01-12 | 2 | Wednesday | Monday | Sunday |
| 2020-01-09 | 2020-01-06 | 2020-01-12 | 2 | Thursday | Monday | Sunday |
| 2020-01-10 | 2020-01-06 | 2020-01-12 | 2 | Friday | Monday | Sunday |
| 2020-01-11 | 2020-01-06 | 2020-01-12 | 2 | Saturday | Monday | Sunday |
| 2020-01-12 | 2020-01-06 | 2020-01-12 | 2 | Sunday | Monday | Sunday |
| 2020-12-21 | 2020-12-21 | 2020-12-27 | 52 | Monday | Monday | Sunday |
| 2020-12-22 | 2020-12-21 | 2020-12-27 | 52 | Tuesday | Monday | Sunday |
| 2020-12-23 | 2020-12-21 | 2020-12-27 | 52 | Wednesday | Monday | Sunday |
| 2020-12-24 | 2020-12-21 | 2020-12-27 | 52 | Thursday | Monday | Sunday |
| 2020-12-25 | 2020-12-21 | 2020-12-27 | 52 | Friday | Monday | Sunday |
| 2020-12-26 | 2020-12-21 | 2020-12-27 | 52 | Saturday | Monday | Sunday |
| 2020-12-27 | 2020-12-21 | 2020-12-27 | 52 | Sunday | Monday | Sunday |
| 2020-12-28 | 2020-12-28 | 2020-12-31 | 53 | Monday | Monday | Thursday |
| 2020-12-29 | 2020-12-28 | 2020-12-31 | 53 | Tuesday | Monday | Thursday |
| 2020-12-30 | 2020-12-28 | 2020-12-31 | 53 | Wednesday | Monday | Thursday |
| 2020-12-31 | 2020-12-28 | 2020-12-31 | 53 | Thursday | Monday | Thursday |
+------------+------------+------------+------+-----------+------------------+----------------+
Calendar and Numbers table references:
Generate a set or sequence without loops - 1 - Aaron Bertrand
Generate a set or sequence without loops - 3 - Aaron Bertrand
The "Numbers" or "Tally" Table: What it is and how it replaces a loop - Jeff Moden
Creating a Date Table/Dimension in SQL Server 2008 - David Stein
Calendar Tables - Why You Need One - David Stein
Creating a date dimension or calendar table in SQL Server - Aaron Bertrand
TSQL Function to Determine Holidays in SQL Server - Tim Cullen
F_TABLE_DATE - Michael Valentine Jones
I have a table with a data as bellow :
+--------+----------+-------+------------+--------------+
| month | code | type | date | PersonID |
+--------+----------+-------+------------+--------------+
| 201501 | 178954 | 3 | 2014-12-3 | 10 |
| 201501 | 178954 | 3 | 2014-12-3 | 10 |
| 201501 | 178955 | 2 | 2014-12-13 | 10 |
| 201501 | 178955 | 2 | 2014-12-13 | 10 |
| 201501 | 178956 | 2 | 2014-12-11 | 10 |
| 201501 | 178958 | 1 | 2014-12-10 | 10 |
| 201501 | 178959 | 2 | 2014-12-12 | 15 |
| 201501 | 178959 | 2 | 2014-12-12 | 15 |
| 201501 | 178954 | 1 | 2014-12-11 | 13 |
| 201501 | 178954 | 1 | 2014-12-11 | 13 |
+--------+----------+-------+------------+--------------+
In my first 6 lines i have the same PersonID in the same Month What i want if i have the same personID in the same Month i want to select the person who have the type is 2 with the recent date in my case the output will be like as bellow:
+--------+--------+------+------------+----------+
| month | code | type| date | PersonID |
+--------+--------+------+------------+----------+
| 201501 | 178955 | 2 | 2014-12-13 | 10 |
| 201501 | 178959 | 2 | 2014-12-12 | 15 |
| 201501 | 178954 | 2 | 2014-12-11 | 13 |
+--------+--------+------+------------+----------+
Also if they are some duplicate rows i don't want to display it
They are any solution to that ?
Simply use GROUP BY:
https://msdn.microsoft.com/de-de/library/ms177673(v=sql.120).aspx
SELECT mont, code, ... FROM tabelname GROUP BY PersonID, date, ...
Note that you have to specifiy all columns in the group by.
SELECT DISTINCT A.month, A.code, A.type, B.date, B.PersonID FROM YourTable A
INNER JOIN (SELECT PersonID, MAX(date) as date FROM YourTable
GROUP BY PersonID) B
ON (A.PersonID = B.PersonID
AND A.date = B.date)
WHERE A.type = 2 ORDER BY B.date DESC, A.PersonID
Just in case you/others are still wondering.
I was trying to answer a question here, where I need to calculate a forecast of sales based on the 3 previous months which either can be actuals or forecast.
Month Actuals Forecast
1 10
2 15
3 17
4 14.00
5 15.33
6 15.44
7 14.93
Month 4 = (10+15+17)/3
Month 5 = (15+17+14)/3
Month 6 = (17+14+15.33)/3
Month 7 = (14+15.33+15.44)/3
I've been trying to do this using a recursive CTE:
;WITH cte([month],forecast) AS (
SELECT 1,CAST(10 AS DECIMAL(28,2))
UNION ALL
SELECT 2,CAST(15 AS DECIMAL(28,2))
UNION ALL
SELECT 3,CAST(17 AS DECIMAL(28,2))
UNION ALL
SELECT
[month]=[month]+1,
forecast=CAST(AVG(forecast) OVER (ORDER BY [month] ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING) AS DECIMAL(28,2))
FROM
cte
WHERE
[month]<=12
)
SELECT * FROM cte WHERE month<=12;
Fiddle: http://sqlfiddle.com/#!6/9ac4a/3
But it doesn't work as expected, as It returns the following result:
| month | forecast |
|-------|----------|
| 1 | 10 |
| 2 | 15 |
| 3 | 17 |
| 4 | (null) |
| 5 | (null) |
| 6 | (null) |
| 7 | (null) |
| 8 | (null) |
| 9 | (null) |
| 10 | (null) |
| 11 | (null) |
| 12 | (null) |
| 3 | (null) |
| 4 | (null) |
| 5 | (null) |
| 6 | (null) |
| 7 | (null) |
| 8 | (null) |
| 9 | (null) |
| 10 | (null) |
| 11 | (null) |
| 12 | (null) |
| 2 | (null) |
| 3 | (null) |
| 4 | (null) |
| 5 | (null) |
| 6 | (null) |
| 7 | (null) |
| 8 | (null) |
| 9 | (null) |
| 10 | (null) |
| 11 | (null) |
| 12 | (null) |
Expected output:
| month | forecast |
|-------|----------|
| 1 | 10 |
| 2 | 15 |
| 3 | 17 |
| 4 | 14.00 |
| 5 | 15.33 |
| 6 | 15.44 |
| 7 | 14.93 |
| 8 | 15.23 |
| 9 | 15.20 |
| 10 | 15.12 |
| 11 | 15.18 |
| 12 | 15.17 |
Can someone tell me what's wrong with this query?
I propose something like this:
WITH T AS
(
SELECT 1 AS [month], CAST(10 AS DECIMAL(28,2)) AS [forecast], CAST(-5 AS DECIMAL(28,2)) AS three_months_ago_forecast, CAST(9 AS decimal(28,2)) AS two_months_ago_forecast, CAST(26 AS decimal(28,2)) as one_month_ago_forecast
UNION ALL
SELECT 2,CAST(15 AS DECIMAL(28,2)), CAST(9 AS decimal(28,2)), CAST(26 AS decimal(28,2)), CAST(10 AS DECIMAL(28,2))
UNION ALL
SELECT 3,CAST(17 AS DECIMAL(28,2)), CAST(26 AS decimal(28,2)), CAST(10 AS DECIMAL(28,2)), CAST(15 AS DECIMAL(28,2))
),
LT AS -- LastForecast
(
SELECT *
FROM T
WHERE [month] = 3
),
FF AS -- Future Forecast
(
SELECT *
FROM LT
UNION ALL
SELECT
FF.[month] + 1 AS [month],
CAST( (FF.forecast * 4 - FF.three_months_ago_forecast) / 3 AS decimal(28,2)) AS forecast,
FF.two_months_ago_forecast as three_months_ago_forecast,
FF.one_month_ago_forecast as two_months_ago_forecast,
FF.forecast as one_month_ago_forecast
FROM FF
WHERE
FF.[month] < 12
)
SELECT * FROM T
WHERE [month] < 3
UNION ALL
SELECT * FROM FF
Output:
+-------+----------+---------------------------+-------------------------+------------------------+
| month | forecast | three_months_ago_forecast | two_months_ago_forecast | one_month_ago_forecast |
+-------+----------+---------------------------+-------------------------+------------------------+
| 1 | 10.00 | -5.00 | 9.00 | 26.00 |
| 2 | 15.00 | 9.00 | 26.00 | 10.00 |
| 3 | 17.00 | 26.00 | 10.00 | 15.00 |
| 4 | 14.00 | 10.00 | 15.00 | 17.00 |
| 5 | 15.33 | 15.00 | 17.00 | 14.00 |
| 6 | 15.44 | 17.00 | 14.00 | 15.33 |
| 7 | 14.92 | 14.00 | 15.33 | 15.44 |
| 8 | 15.23 | 15.33 | 15.44 | 14.92 |
| 9 | 15.20 | 15.44 | 14.92 | 15.23 |
| 10 | 15.12 | 14.92 | 15.23 | 15.20 |
| 11 | 15.19 | 15.23 | 15.20 | 15.12 |
| 12 | 15.18 | 15.20 | 15.12 | 15.19 |
+-------+----------+---------------------------+-------------------------+------------------------+
Try this
WITH cte
AS (SELECT *
FROM (VALUES (1,10,NULL),
(2,15,NULL),
(3,17,NULL),
(4,NULL,14.00),
(5,NULL,15.33),
(6,NULL,15.44),
(7,NULL,14.93)) tc (month, act, fore))
SELECT mon,avg(res)
FROM cte a
CROSS apply (SELECT TOP 3 ( COALESCE(a.act, a.fore) ) AS res,
b.month AS mon
FROM cte b
WHERE a.month < b.month
ORDER BY a.month DESC) cs
GROUP BY mon
ORDER BY mon
or in Sql Server 2012+ use this
SELECT
[month]=[month]+1,
forecast=CAST(AVG(COALESCE(act,fore)) OVER (ORDER BY [month] ROWS BETWEEN 3 PRECEDING AND CURRENT row ) AS DECIMAL(28,2))
FROM
cte
I have a table that holds values for particular months:
| MFG | DATE | FACTOR |
-----------------------------
| 1 | 2013-01-01 | 1 |
| 2 | 2013-01-01 | 0.8 |
| 2 | 2013-02-01 | 1 |
| 2 | 2013-12-01 | 1.55 |
| 3 | 2013-01-01 | 1 |
| 3 | 2013-04-01 | 1.3 |
| 3 | 2013-05-01 | 1.2 |
| 3 | 2013-06-01 | 1.1 |
| 3 | 2013-07-01 | 1 |
| 4 | 2013-01-01 | 0.9 |
| 4 | 2013-02-01 | 1 |
| 4 | 2013-12-01 | 1.8 |
| 5 | 2013-01-01 | 1.4 |
| 5 | 2013-02-01 | 1 |
| 5 | 2013-10-01 | 1.3 |
| 5 | 2013-11-01 | 1.2 |
| 5 | 2013-12-01 | 1.5 |
What I would like to do is pivot these using a calendar table (already defined):
And finally, cascade the NULL columns to use the previous value.
What I've got so far is a query that will populate the NULLs with the last value for mfg = 3. Each mfg will always have a value for the first of the year. My question is; how do I pivot this and extend to all mfg?
SELECT c.[date],
f.[factor],
Isnull(f.[factor], (SELECT TOP 1 factor
FROM factors
WHERE [date] < c.[date]
AND [factor] IS NOT NULL
AND mfg = 3
ORDER BY [date] DESC)) AS xFactor
FROM (SELECT [date]
FROM calendar
WHERE Datepart(yy, [date]) = 2013
AND Datepart(d, [date]) = 1) c
LEFT JOIN (SELECT [date],
[factor]
FROM factors
WHERE mfg = 3) f
ON f.[date] = c.[date]
Result
| DATE | FACTOR | XFACTOR |
---------------------------------
| 2013-01-01 | 1 | 1 |
| 2013-02-01 | (null) | 1 |
| 2013-03-01 | (null) | 1 |
| 2013-04-01 | 1.3 | 1.3 |
| 2013-05-01 | 1.2 | 1.2 |
| 2013-06-01 | 1.1 | 1.1 |
| 2013-07-01 | 1 | 1 |
| 2013-08-01 | (null) | 1 |
| 2013-09-01 | (null) | 1 |
| 2013-10-01 | (null) | 1 |
| 2013-11-01 | (null) | 1 |
| 2013-12-01 | (null) | 1 |
SQL Fiddle
Don't know if you need the dates to be dynamic from the calender table or if mfg can be more than 5 but this should give you some ideas.
select *
from (
select c.date,
t.mfg,
(
select top 1 f.factor
from factors as f
where f.date <= c.date and
f.mfg = t.mfg and
f.factor is not null
order by f.date desc
) as factor
from calendar as c
cross apply(values(1),(2),(3),(4),(5)) as t(mfg)
) as t
pivot (
max(t.factor) for t.date in ([20130101], [20130201], [20130301],
[20130401], [20130501], [20130601],
[20130701], [20130801], [20130901],
[20131001], [20131101], [20131201])
) as P
SQL Fiddle