ms sql group by 2 hours - sql-server

i have this ms-sql code below, running on sql server 2017:
DECLARE #report_int TABLE
(
atimestamp DATETIME,
adate DATE,
ahour INT,
intvalue INT,
attribute CHAR(254),
total INT
)
INSERT INTO #report_int
SELECT
el.servertimestamp,
cast(el.servertimestamp as date) adate,
(DATEPART(HH,el.servertimestamp)) ahour,
el.intvalue,
el.attributes,
0
FROM
eventlog el
where
el.servertimestamp BETWEEN '6/29/2017'
AND DATEADD(dd, +1, '6/29/2019')
and (attributes LIKE '%N<=>OlympieioIn%' OR attributes LIKE '%N<=>OlympieioOut%')
order by
el.servertimestamp
DECLARE #report TABLE
(
adate datetime,
ahour int,
came_in bigint,
gone_out bigint
)
--fill report with dates and hours
INSERT INTO #report (adate, ahour) SELECT DISTINCT adate, ahour FROM #report_int
--reset
UPDATE #report set came_in = 0, gone_out = 0
--update in totals, min and max times
UPDATE #report SET came_in = i.total from
#report r,
(
select adate, ahour, COUNT(*) total
FROM #report_int
WHERE attribute LIKE 'N<=>OlympieioIn'
GROUP BY adate, ahour
) i
WHERE
i.adate = r.adate
and i.ahour = r.ahour
--update out totals
UPDATE #report SET gone_out = i.total from
#report r,
(
SELECT adate, ahour, COUNT(*) total
FROM #report_int
WHERE attribute LIKE 'N<=>OlympieioOut'
GROUP BY adate, ahour
) i
where
i.adate = r.adate
and i.ahour = r.ahour
select
adate,
ahour,
RIGHT('00'+cast(ahour as varchar),2)+':00 - '+RIGHT('00'+cast(ahour+1 as varchar),2)+':00' timeframe,
came_in,
gone_out
from
#report
This code shows how many people came in or gone out of a building every day. As you can see, the amount of people who come in and go out, are displayed in an hourly grouping.
Below is a screenshot of the executed code.
What i want to do is group these data in a 2-hour grouping.For example, the grouping of the 3rd and 4th hour of the executed code above, will be:
DATE(adate): 2018-05-12
Hour(ahour): 15
Timeframe: 15:00-17:00
came_in: 0
gone_out: 2
Your help will be much appreciated,
thank you.

One way to get a datetime to the last 2 hour slot would be to use:
DATEADD(HOUR, DATEDIFF(HOUR, 0, YourDateColumn) - (DATEDIFF(HOUR, 0, YourDateColumn) % 2), 0)
If we then take some (random) sample data, you get the following:
WITH VTE AS (
SELECT CONVERT(datetime2(0),V.DT) AS DT
FROM (VALUES('2018-07-04T00:11:32'),('2018-07-04T01:17:12'),('2018-07-04T02:00:01'),('2018-07-04T07:49:59'),('2018-07-04T07:59:58'),('2018-07-04T08:00:00')) V(DT))
SELECT DT, DATEADD(HOUR, DATEDIFF(HOUR, 0, DT) - (DATEDIFF(HOUR, 0, DT) % 2), 0) AS DT2h
FROM VTE;
Which returns:
DT DT2h
--------------------------- -----------------------
2018-07-04 00:11:32 2018-07-04 00:00:00.000
2018-07-04 01:17:12 2018-07-04 00:00:00.000
2018-07-04 02:00:01 2018-07-04 02:00:00.000
2018-07-04 07:49:59 2018-07-04 06:00:00.000
2018-07-04 07:59:58 2018-07-04 06:00:00.000
2018-07-04 08:00:00 2018-07-04 08:00:00.000
If you wanted a COUNT by 2 hour slot:
WITH VTE AS (
SELECT CONVERT(datetime2(0),V.DT) AS DT
FROM (VALUES('2018-07-04T00:11:32'),('2018-07-04T01:17:12'),('2018-07-04T02:00:01'),('2018-07-04T07:49:59'),('2018-07-04T07:59:58'),('2018-07-04T08:00:00')) V(DT))
SELECT DATEADD(HOUR, DATEDIFF(HOUR, 0, DT) - (DATEDIFF(HOUR, 0, DT) % 2), 0) AS DT2h, COUNT(DT) AS C
FROM VTE
GROUP BY DATEADD(HOUR, DATEDIFF(HOUR, 0, DT) - (DATEDIFF(HOUR, 0, DT) % 2), 0);
Which returns:
DT2h C
----------------------- -----------
2018-07-04 00:00:00.000 2
2018-07-04 02:00:00.000 1
2018-07-04 06:00:00.000 2
2018-07-04 08:00:00.000 1

I would simply manually create a timetable and join it.
The timetable would have 24 rows (one for each hour) and could look like this:
hour hourtimeframe twohourtimeframe
13 13:00 - 14:00 13:00 - 15:00

Related

T-SQL : set start of the week from named date and show week nr of the date

I have a code that traces a number of week of the date in a period that set by parameters
CREATE TABLE #test
(
job int,
dateL datetime
)
INSERT INTO #test
VALUES (1, '2021-10-04'),
(2, '2021-10-05'),
(3, '2021-10-11'),
(4, '2021-10-12')
DECLARE #startdate datetime = '2021-10-05',
#enddate datetime = '2021-12-03'
SELECT
dateL,
(CASE
WHEN t.DateL BETWEEN #startDate and #endDate
THEN (DATEDIFF(wk,#startDate, t.DateL)) + 1
ELSE -1
END) AS WeekNumber
FROM
#test t
DROP TABLE #test
The results of this
dateL WeekNumber
---------------------------------------
2021-10-04 00:00:00.000 -1
2021-10-05 00:00:00.000 1
2021-10-11 00:00:00.000 2
2021-10-12 00:00:00.000 2
But it now quite what I need, and I don't understand how to set this to start count from #startDate.
So it should count like:
05.10 - 11.10 - first week
12.20 - 18.10 - second week
and so on,
So it would look like that
dateL WeekNumber
-----------------------------------
2021-10-04 00:00:00.000 -1
2021-10-05 00:00:00.000 1
2021-10-11 00:00:00.000 *1*
2021-10-12 00:00:00.000 2
I tried to set ##datefirst to
datepart(weekday, #startDate)
but it just ignores me.
Could someone may be recommend something, thanks!

Calculate time between startdate and enddate and subtracting days that have no worktime

My goal is to check if an email is answered within 24 hours during workdays. de definition of a workday is if there is time registered in another table. this because we sometimes work on a Saturday or a Sunday or to exclude holidays. I made a view from that table that gives a 1 if the date has worktime or a 0 if there is no worktime registered.
DateWorked
HasWorked
2021-04-01 00:00:00.000
1
2021-04-02 00:00:00.000
1
2021-04-03 00:00:00.000
1
2021-04-04 00:00:00.000
0
2021-04-05 00:00:00.000
1
So for example a few situations:
1. MailIncoming: 2021-04-01 16:30:00, MailAnswering: 2021-04-02 14:00:00
This one is easy, I don't have to subtract anything and the mail is answered within 24 hours.
2. MailIncoming: 2021-04-01 09:30:00, MailAnswering: 2021-04-03 14:00:00
This one is also easy, I don't have to subtract anything and the mail is not answered within 24 hours.
3. MailIncoming: 2021-04-03 12:30:00, MailAnswering: 2021-04-05 10:00:00
There is 1 day where no one has worked, so I need to subtract 1 whole day from the total time, and in that case the email is answered within 24 hours during workdays.
4. MailIncoming: 2021-04-04 11:00:00, MailAnswering: 2021-04-05 18:00:00
The remaining 13 hours from 04 do not count toward the '24 hours during workdays' so the email is answered within 24 during workdays.
Also, there can be multiple dates with zero after each other.
So the outcome I'm looking for is:
MailIncoming
MailAnswering
TotalTime
TotalTimeWithoutDaysNotWorked
2021-04-04 11:00:00.000
2021-04-05 18:00:00.000
31
18
How can I calculate this last column? Or am I approaching this in the wrong way?
The query needs a way to generate calculated dates between MailIncoming and MailAnswering so there can be a LEFT JOIN (or INNER JOIN) to the WorkingDay table. In this case the query uses dbo.fnTally which is known to be a fast and efficient way to generate rows.
tables
drop table if exists #WorkingDay;
go
create table #WorkingDay(
DateWorked Date,
HasNotWorked int);
drop table if exists #MailIncoming;
go
create table #MailIncoming(
MailIncoming DateTime,
MailAnswering DateTime);
insert into #WorkingDay values
('2021-04-01', 0),
('2021-04-02', 0),
('2021-04-03', 0),
('2021-04-04', 1),
('2021-04-05', 0),
('2021-04-06', 0);
insert into #MailIncoming values
('2021-04-01 16:30:00', '2021-04-02 14:00:00'),
('2021-04-01 09:30:00', '2021-04-03 14:00:00'),
('2021-04-03 12:30:00', '2021-04-05 10:00:00'),
('2021-04-04 11:00:00', '2021-04-05 18:00:00');
dbo.fnTally
CREATE FUNCTION [dbo].[fnTally]
/**********************************************************************************************************************
Jeff Moden Script on SSC: https://www.sqlservercentral.com/scripts/create-a-tally-function-fntally
**********************************************************************************************************************/
(#ZeroOrOne BIT, #MaxN BIGINT)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN WITH
H2(N) AS ( SELECT 1
FROM (VALUES
(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
)V(N)) --16^2 or 256 rows
, H4(N) AS (SELECT 1 FROM H2 a, H2 b) --16^4 or 65,536 rows
, H8(N) AS (SELECT 1 FROM H4 a, H4 b) --16^8 or 4,294,967,296 rows
SELECT N = 0 WHERE #ZeroOrOne = 0 UNION ALL
SELECT TOP(#MaxN)
N = ROW_NUMBER() OVER (ORDER BY N)
FROM H8
;
query
select mi.MailIncoming, mi.MailAnswering,
avg(datediff(hour, MailIncoming, MailAnswering)) hrs_to_ans,
sum(case when w.HasNotWorked=1 and
v.calc_dt > mi_dt.inc_dt and
v.calc_dt < mi_dt.ans_dt
then -24
when w.HasNotWorked=1
then datediff(hour, dateadd(day, 1, mi_dt.inc_dt), mi.MailIncoming)
else 0 end) hrs_to_sub
from #MailIncoming mi
cross apply (values (cast(MailIncoming as date),
cast(MailAnswering as date))) mi_dt(inc_dt, ans_dt)
cross apply dbo.fnTally(0, datediff(day, mi.MailIncoming, mi.MailAnswering)) fn
cross apply (values (dateadd(day, fn.n, mi_dt.inc_dt))) v(calc_dt)
left join #WorkingDay w on v.calc_dt=w.DateWorked
group by mi.MailIncoming, mi.MailAnswering
order by mi.MailIncoming;
MailIncoming MailAnswering hrs_to_ans hrs_to_sub
2021-04-01 09:30:00.000 2021-04-03 14:00:00.000 53 0
2021-04-01 16:30:00.000 2021-04-02 14:00:00.000 22 0
2021-04-03 12:30:00.000 2021-04-05 10:00:00.000 46 -24
2021-04-04 11:00:00.000 2021-04-05 18:00:00.000 31 -13
I suggest you to use a column HasNotWorked, so the tables are
create table WorkingDay(DateWorked Date, HasNotWorked int);
create table MailIncoming(MailIncoming DateTime, MailAnswering DateTime);
and the rows
insert into WorkingDay values('2021-04-01', 0);
insert into WorkingDay values('2021-04-02', 0);
insert into WorkingDay values('2021-04-03', 0);
insert into WorkingDay values('2021-04-04', 1);
insert into WorkingDay values('2021-04-05', 0);
insert into WorkingDay values('2021-04-06', 0);
insert into MailIncoming values('2021-04-04 11:00:00.000', '2021-04-06 18:00:00.000');
I want calculate the start date. If is in working day, we must consider the hour of the mail, else the first working day with
case when
(select HasNotWorked from WorkingDay where DateWorked = convert(date, MailIncoming)) = 1 then
(select min(DateWorked) from WorkingDay where DateWorked > MailIncoming and HasNotWorked = 0)
else MailIncoming end as startDate
and discard the day that are not working day
((select sum(HasNotWorked) from WorkingDay where DateWorked between convert(date, startDate)
and convert(date, MailAnswering)
) * 24) as numNotWorkingDay
so the query could be
select startDate, MailAnswering, MailIncoming, hour, numNotWorkingDay, hour - numNotWorkingDay hourWitoutWorkingDay
from (
select
MailAnswering, startDate, MailIncoming,
DateDiff("hh", startDate, MailAnswering) hour,
((select sum(HasNotWorked) from WorkingDay where DateWorked between convert(date, startDate)
and convert(date, MailAnswering)
) * 24) as numNotWorkingDay
from (
select *,
case when
(select HasNotWorked from WorkingDay where DateWorked = convert(date, MailIncoming)) = 1 then
(select min(DateWorked) from WorkingDay where DateWorked > MailIncoming and HasNotWorked = 0)
else MailIncoming end as startDate
from MailIncoming) as startCalc
) as calcTable;
sqlfiddle

Select part of duration that occurs in specific hour of day

In SQL Server, I have a table of processes with a starttime and an endtime, from which I can calculate a duration using DATEDIFF.
Name StartTime EndTime
------------------------------------------------
process1 2016-10-10 11:10 2016-10-10 11:20
process2 2016-10-10 11:40 2016-10-10 12:30
How can I select the timespan of the process duration that occured in specific hours of the day (11 and 12) in seconds?
So process1 would be 10 minutes in hour 11.
process2 would be 20 minutes in hour 11, 30 minutes in hour 12.
Here's a sample that will generate hourly records for each source record. It uses a recursive CTE to move from the StartTime of each record through the EndTime. It may need some slight modifications to make it work in your case, but hopefully you can get the idea of how this method works.
Note that, as shown in the sample below, this will work properly even if the time span crosses a date boundary.
--==================================================================================
-- Do some quick setup to get a temporary table populated with data to use:
--==================================================================================
IF OBJECT_ID('tempdb..#ProcessHistory', 'U') IS NOT NULL DROP TABLE #ProcessHistory;
CREATE TABLE #ProcessHistory (
Name VARCHAR(20),
StartTime DATETIME,
EndTime DATETIME
)
INSERT INTO #ProcessHistory
VALUES ('process1', '2016-10-10 11:10', '2016-10-10 11:20'),
('process2', '2016-10-10 11:40', '2016-10-10 12:30'),
('process3', '2016-10-10 22:21', '2016-10-11 02:36');
--==================================================================================
-- Use a recursive CTE to generate hourly data for each record:
--==================================================================================
WITH HourlyData AS (
-- Anchor:
SELECT
ph.Name [ProcessName],
ph.StartTime [StartTime],
ph.EndTime [EndTime],
-- Get the current hour with date:
DATEADD(MINUTE, -DATEPART(MINUTE, ph.StartTime), ph.StartTime) [CurrentHour],
-- Calculate the next hour for use later:
DATEADD(MINUTE, 60 - DATEPART(MINUTE, ph.StartTime), ph.StartTime) [NextHour],
-- Determine how many minutes the process was active this hour:
CASE
WHEN DATEDIFF(MINUTE, ph.StartTime, ph.EndTime) > 60 - DATEPART(MINUTE, ph.StartTime)
THEN 60 - DATEPART(MINUTE, ph.StartTime)
ELSE DATEDIFF(MINUTE, ph.StartTime, ph.EndTime)
END [Minutes]
FROM #ProcessHistory ph
UNION ALL
-- Recurse:
SELECT
hd.ProcessName,
hd.StartTime,
hd.EndTime,
hd.NextHour [CurrentHour],
DATEADD(HOUR, 1, hd.NextHour) [NextHour],
-- Determine how many minutes the process was active this hour:
CASE
WHEN DATEDIFF(MINUTE, hd.NextHour, hd.EndTime) < 60
THEN DATEDIFF(MINUTE, hd.NextHour, hd.EndTime)
ELSE 60
END
FROM HourlyData hd
WHERE hd.NextHour < hd.EndTime
)
SELECT
hd.ProcessName,
hd.CurrentHour [HourWithDate],
CONVERT(DATE, hd.CurrentHour) [Date],
DATEPART(HOUR, hd.CurrentHour) [Hour],
hd.Minutes
FROM HourlyData hd
ORDER BY
hd.ProcessName,
hd.CurrentHour;
The output from the above sample would look like this:
ProcessName HourWithDate Date Hour Minutes
process1 2016-10-10 11:00:00.000 2016-10-10 11 10
process2 2016-10-10 11:00:00.000 2016-10-10 11 20
process2 2016-10-10 12:00:00.000 2016-10-10 12 30
process3 2016-10-10 22:00:00.000 2016-10-10 22 39
process3 2016-10-10 23:00:00.000 2016-10-10 23 60
process3 2016-10-11 00:00:00.000 2016-10-11 0 60
process3 2016-10-11 01:00:00.000 2016-10-11 1 60
process3 2016-10-11 02:00:00.000 2016-10-11 2 36
To handle general cases, you could try something like
--drop table #processes
CREATE TABLE #processes
(
name varchar(50),
StartTime Datetime,
EndTime DateTime
);
insert #processes VALUES('proc1','20161010 11:10','20161010 11:20');
insert #processes VALUES('proc2','20161010 11:40','20161010 12:20');
insert #processes VALUES('proc3','20161010 10:40','20161010 12:20');
;WITH HRS AS (SELECT 0 HR
UNION ALL
SELECT HR + 1 FROM HRS WHERE HR < 23),
MINS AS (SELECT 0 MN
UNION ALL
SELECT MN + 1 FROM MINS WHERE MN < 59),
TIMES AS (SELECT HR,MN FROM HRS CROSS JOIN MINS)
SELECT name,starttime,endtime,Count(0) AS mins FROM #processes
JOIN TIMES
ON (HR > datepart(hh,Starttime)
OR HR = datepart(hh,Starttime) AND MN >= datepart(n,STARTtIME))
AND
(HR < datepart(hh, EndTime)
OR HR = datepart(hh, EndTime) AND MN < datepart(n,EndTime))
WHERE HR = 11 --hour is 11
GROUP BY name,
starttime,
endtime
drop table #processes;
create table #temp (Name varchar(5), starttime datetime, EndTime datetime)
insert into #temp values(1,'2016-10-10 11:10','2016-10-10 11:20' )
insert into #temp values(2,'2016-10-10 11:40','2016-10-10 12:30' )
insert into #temp values(2,'2016-10-10 10:40','2016-10-10 11:30' )
insert into #temp values(2,'2016-10-10 10:40','2016-10-10 12:30' )
DECLARE #firstTime time ,#secondTime time
set #firstTime ='11:00'
set #secondTime ='12:00'
select
CASE WHEN CONVERT(time(0), starttime) < #firstTime AND CONVERT(time(0), EndTime) > #secondTime THEN DATEDIFF(ss,#firstTime,#secondTime)
WHEN CONVERT(time(0), EndTime) > #secondTime THEN DATEDIFF(ss,CONVERT(time(0), starttime),#secondTime)
WHEN CONVERT(time(0), starttime) < #firstTime THEN DATEDIFF(ss,CONVERT(time(0), EndTime),#secondTime)
ELSE DATEDIFF(ss,starttime,EndTime)
END
from #temp
SELECT CAST(DATEADD(MINUTE,DATEDIFF(MINUTE,'2016-10-10 11:10','2016-10-10 12:20'),'2011-01-01 00:00:00.000') AS TIME)
as timeDifference
With timeDifference -
SELECT CAST(DATEADD(MINUTE,DATEDIFF(MINUTE,StartTime,EndTime),'2011-01-01 00:00:00.000') AS TIME)
as timeDifference
from #YourTableName
With Days and TimeDiffernece
declare #start_time as varchar(150);
declare #end_time as varchar(150);
set #start_time='2016-10-10 10:10';
set #end_time='2016-10-12 12:10'
SELECT datediff(day,#start_time,#end_time) as dayDifference,
CAST(DATEADD(MINUTE,DATEDIFF(MINUTE,#start_time,#end_time),'2011-01-01 00:00:00') AS TIME(0))
as timeDifference
I think this does the trick, but it's pretty ugly. Maybe someone can do it more elegantly?
SELECT
case
when HOUR(starttime) < 11 AND HOUR(endtime) = 11 then minute(endtime)
when HOUR(starttime) < 11 AND HOUR(endtime) > 11 then 60
when HOUR(starttime) = 11 AND HOUR(endtime) = 11 then minute(endtime) - minute(starttime)
when HOUR(starttime) = 11 AND HOUR(endtime) > 11 then 60 - minute(starttime)
else 0
end AS ProcessTimeHour_11,
case
when HOUR(starttime) < 12 AND HOUR(endtime) = 12 then minute(endtime)
when HOUR(starttime) < 12 AND HOUR(endtime) > 12 then 60
when HOUR(starttime) = 12 AND HOUR(endtime) = 12 then minute(endtime) - minute(starttime)
when HOUR(starttime) = 12 AND HOUR(endtime) > 12 then 60 - minute(starttime)
else 0
end AS ProcessTimeHour_12
from StuffAndThings

Split date range into one row per month in SQL Server pt2

This question relates to this post: Split date range into one row per month in sql server
But I'm new to Stackoverflow and new users are not allowed to place comments to provided solutions.That's why I am now adding this as a new question.
The question was this:
I have a table with two column called "from_date" and "to_date"
The table looks like this:
Table (I am not allowed to embed my picture here, so please click the link)
I want result like:-
from_date || to_date
----------- ------------
2013-11-25 || 2013-11-30
2013-12-01 || 2013-12-05
That date is splits from 2013-11-25 to 2013-11-30 and another date split from 2013-12-01 to 2013-12-05... Is it possible to split like this ?
Aaron Bertrand's solution is this:
DECLARE #d TABLE(from_date DATE, to_date DATE);
INSERT #d VALUES ('2013-11-25','2013-12-05');
;WITH n(n) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY [object_id])-1
FROM sys.all_columns
),
d(n,f,t,md,bp,ep) AS
(
SELECT n.n, d.from_date, d.to_date,
DATEDIFF(MONTH, d.from_date, d.to_date),
DATEADD(MONTH, n.n, DATEADD(DAY, 1-DAY(from_date), from_date)),
DATEADD(MONTH, n.n+1, DATEADD(DAY, 0-DAY(from_date), from_date))
FROM n
INNER JOIN #d AS d ON d.to_date >= DATEADD(MONTH, n.n-1, d.from_date)
)
SELECT original_from_date = f, original_to_date = t,
new_from_date = CASE n WHEN 0 THEN f ELSE bp END,
new_to_date = CASE n WHEN md THEN t ELSE ep END
FROM d
WHERE md >= n
ORDER BY original_from_date, new_from_date;
Results:
original_from_date original_to_date new_from_date new_to_date
------------------ ---------------- ------------- -----------
2013-11-25 2013-12-05 2013-11-25 2013-11-30
2013-11-25 2013-12-05 2013-12-01 2013-12-05
Aaron Bertrand's answer is a super powerful query, but it seems it only works well if the startdate is in a month following a month with 31 days. If you change the startdate to, for example, 2013-5-25 (following April, with only 30 days), the new_to_date values are incorrect: all full months suddenly get a max of 30 days then:
original_from_date original_to_date new_from_date new_to_date
2013-05-25 2013-12-05 2013-05-25 2013-05-30
2013-05-25 2013-12-05 2013-06-01 2013-06-30
2013-05-25 2013-12-05 2013-07-01 2013-07-30
2013-05-25 2013-12-05 2013-08-01 2013-08-30
2013-05-25 2013-12-05 2013-09-01 2013-09-30
2013-05-25 2013-12-05 2013-10-01 2013-10-30
2013-05-25 2013-12-05 2013-11-01 2013-11-30
2013-05-25 2013-12-05 2013-12-01 2013-12-05
Can anyone edit the query so that end dates are correct (28,30,31 days, and 29 feb leap) regardless of what month the start date is in? I can't figure out how to do it.
Many thanks, Martijn Vermunt
The answer has been provided, the query has been edited and made perfect. Thank you #Aaron Bertrand
for updating the query!
DECLARE #d TABLE(from_date DATE, to_date DATE);
INSERT #d VALUES ('2013-11-25','2013-12-05');
;WITH n(n) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY [object_id])-1 FROM sys.all_columns
),
d(n,f,t,md,bp,ep) AS
(
SELECT n.n, d.from_date, d.to_date,
DATEDIFF(MONTH, d.from_date, d.to_date),
DATEADD(MONTH, n.n, DATEADD(DAY, 1-DAY(from_date), from_date)),
DATEADD(DAY, -1, DATEADD(MONTH, 1, DATEADD(MONTH, n.n,
DATEADD(DAY, 1-DAY(from_date), from_date))))
FROM n INNER JOIN #d AS d
ON d.to_date >= DATEADD(MONTH, n.n-1, d.from_date)
)
SELECT original_from_date = f, original_to_date = t,
new_from_date = CASE n WHEN 0 THEN f ELSE bp END,
new_to_date = CASE n WHEN md THEN t ELSE ep END
FROM d WHERE md >= n
ORDER BY original_from_date, new_from_date;

Fetching dates monthly from the table

Can you help out with a problem
I have table price table which has daily prices starting 31st Dec 2010 till todays date.The table contains daily prices
2009-12-31 00:00:00.000 1.0020945351
2010-01-01 00:00:00.000 1.0021009300
2010-01-04 00:00:00.000 1.0021910181
2010-01-05 00:00:00.000 1.0022005986
2010-01-06 00:00:00.000 1.0022428696
2010-01-07 00:00:00.000 1.0022647147
2010-01-08 00:00:00.000 1.0022842726
2010-01-11 00:00:00.000 1.0023374302
2010-01-12 00:00:00.000 1.0023465374
2010-01-13 00:00:00.000 1.0023638081
2010-01-14 00:00:00.000 1.0023856533
2010-01-00 00:00:00.000 1.0024083955
2010-01-18 00:00:00.000 1.0024779677
2010-01-19 00:00:00.000 1.0025020553
2010-01-20 00:00:00.000 1.002521135
2010-01-21 00:00:00.000 1.0025420688
2010-01-22 00:00:00.000 1.0025593397
2010-01-25 00:00:00.000 1.0026180146
2010-01-26 00:00:00.000 1.002637573
2010-01-27 00:00:00.000 1.0026648447
2010-01-28 00:00:00.000 1.0026957934
2010-01-29 00:00:00.000 1.0027267421
2010-02-01 00:00:00.000 1.0028195885
2010-02-02 00:00:00.000 1.0028573523
2010-02-03 00:00:00.000 1.0028964611
2010-02-04 00:00:00.000 1.00293557
2010-02-05 00:00:00.000 1.002973334
2010-02-08 00:00:00.000 1.0030879717
2010-02-09 00:00:00.000 1.0031279777
2010-02-10 00:00:00.000 1.003171166
2010-02-11 00:00:00.000 1.0032007452
2010-02-12 00:00:00.000 1.0032575895
2010-02-00 00:00:00.000 1.0033749191
2010-02-1 00:00:00.000 1.0034140292
2010-02-17 00:00:00.000 1.003452691
2010-02-18 00:00:00.000 1.0034918013
2010-02-19 00:00:00.000 1.0035395633
2010-02-22 00:00:00.000 1.0036664439
2010-02-23 00:00:00.000 1.0037042097
2010-02-24 00:00:00.000 1.0037510759
2010-02-25 00:00:00.000 1.0038001834
2010-02-26 00:00:00.000 1.003850077
I need to write a query to get index based on
(Last day of current month/Previous month last day) - 1 * 100.So that output comes something like this
31-Jan-10 0.01%
28-Feb-10 0.02%
31-Mar-10 0.00%
Following is one of the solution I thought about however please share best ideas to implement this problem
Extract last day of all the months with values into a temp table and then order by dates so that they subtract and put the values into another temp table
Looking forward to your help.
Try this....
DECLARE #StartDate DATETIME = '2010-01-01',
#EndDate DATETIME = GETDATE();
WITH data AS (
SELECT 1 AS i, CONVERT(DATETIME, NULL) AS StartDate, DATEADD(MONTH, 0, #StartDate) - 1 AS EndDate
UNION ALL
SELECT i + 1, data.EndDate, DATEADD(MONTH, i, #StartDate) - 1 AS EndDate
FROM data
WHERE DATEADD(MONTH, i, #StartDate) - 1 < #EndDate
)
SELECT (
((SELECT TOP 1 Rate FROM RateTable WHERE Date <= data.EndDate ORDER BY Date DESC) /
(SELECT TOP 1 Rate FROM RateTable WHERE Date <= data.StartDate ORDER BY Date DESC)- 1) * 100)
FROM DATA -- parenthesis were causing issues
WHERE data.StartDate IS NOT NULL
OPTION (MAXRECURSION 10000);
You'll need to replace the
(SELECT Rate FROM RateTable WHERE Date = data.StartDate)
and
(SELECT Rate FROM RateTable WHERE Date = data.EndDate)
With the values for your rate table. as you didn't mention column and table names in your question.
rwking indicated that there might be gaps in the rates table that would cause issues.
I've modified the subquery to bring back the first rate on or nearest the start and end dates.
Hope that helps
You can use the LAG function introduced in SQL2012 to make it a bit easier:
WITH DataWithOrder AS
(
SELECT DateField, PriceField,
ROW_NUMBER() OVER(PARTITION BY YEAR(DateField), Month(DateField) ORDER BY DateField DESC) AS Pos
FROM PriceTable
)
SELECT
DateField,
PriceField,
LAG(PriceField) OVER(ORDER BY DateField) AS PriceLastMonth,
((PriceField / LAG(PriceField) OVER(ORDER BY DateField)) - 1) * 100 AS PCIncrease
FROM DataWithOrder
WHERE Pos = 1
ORDER BY DateField
I took a very different approach than the other guy. His is more elegant and would work better if the daily data does represent every single day of every month. If there are gaps in days, however, as your sample data represents, you can try the following code.
with cte as (select mydate
, price
, ROW_NUMBER() over(partition by YEAR(mydate), MONTH(mydate)
order by day(mydate) desc) row_n
from #temp)
select mydate, price, ROW_NUMBER() over(order by mydate desc) row_num
into #temp2
from cte
where row_n = 1
alter table #temp2
add idx float
declare #counter int = 1
while #counter < (select MAX(row_num)+1 from #temp2)
begin
update t2
set t2.idx = ((t2.price/t3.price)-1)*100
from #temp2 t2 left join
#temp2 t3 on 1 = 1
where t2.row_num = #counter and t3.row_num = #counter + 1
set #counter = #counter + 1
end
select mydate, idx
from #temp2
As the other poster mentioned, you didn't provide column or table names. My process was to insert your data into a table called [#temp] with column names [mydate] and [price].
Also, the data sample you provided contains two invalid dates that I changed to arbitrary dates just for the purposes of getting code to run. (2010-01-00 and 2010-02-00)

Resources