Get every hour for a time range - sql

So what I am trying to is generate all the hours that are inside a specific time range.
So given the range 11 AM to 2:00 PM, I would get:
11:00 AM
12:00 PM
1:00 PM
2:00 PM
I am trying to avoid having to store every specific hour a store might be open and just store the range (I need to compare the hours against other times)
Thanks

No loops, recursive CTEs or numbers table required.
DECLARE
#start TIME(0) = '11:00 AM',
#end TIME(0) = '2:00 PM';
WITH x(n) AS
(
SELECT TOP (DATEDIFF(HOUR, #start, #end) + 1)
rn = ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_columns ORDER BY [object_id]
)
SELECT t = DATEADD(HOUR, n-1, #start) FROM x ORDER BY t;

You could use a recursive CTE. This would generate the hours between 11 and 14:
;with Hours as
(
select 11 as hr
union all
select hr + 1
from Hours
where hr < 14
)
select *
from Hours
Live example at SQL Fiddle.

If you have a numbers table (click the link to create one if you don't)...
create table test(
startTime time
, endTime time
)
insert into test
select '11:00', '14:00'
select
dateadd(hh, n.n, t.startTime) as times
from test t
inner join Numbers n
-- assuming your numbers start at 1 rather than 0
on n.n-1 <= datediff(hh, t.startTime, t.endTime)
If this is specialized, you can create an hours table with just 24 values.
create table HoursInADay(
[hours] time not null
, constraint PK_HoursInADay primary key ([hours])
)
-- insert
insert into HoursInADay select '1:00'
insert into HoursInADay select '2:00'
insert into HoursInADay select '3:00'
insert into HoursInADay select '4:00'
insert into HoursInADay select '5:00'
insert into HoursInADay select '6:00'
insert into HoursInADay select '7:00'
...
select
h.[hours]
from test t
inner join HoursInADay h
on h.[hours] between t.startTime and t.endTime

The easiest way I can think of to do this is to have only 1 permanent table with a list of all hours; 24 entries total.
Create table dbo.Hours (Hourly_Time Time NOT NULL)
Insert into dbo.Hours ...
Then, given times A & B:
select * from dbo.Hours where Hourly_Time<=A and Hourly_Time>=B

#Andomar Thanks a lot, you helped me, there is my add above your code.
*----------------------------
create view vw_hoursalot as
with Hours as
(
select DATEADD(
dd, 0, DATEDIFF(
dd, 0, DATEADD (
year , -5 , getDate()
)
)
) as dtHr
union all
select DATEADD (minute , 30 , dtHr )
from Hours
where dtHr < DATEADD(
dd, 0, DATEDIFF(
dd, 0, DATEADD (
year , +5 , getDate()
)
)
)
)
select * from Hours
----------------------------
select * from vw_hoursalot option (maxrecursion 0)
----------------------------*

Related

Create View in SQL Server to create multiple rows for each date,with calculated date and identifier column

I need to create a VIEW/Select statement that will take a start date, and create 3 different rows for each date. One row calculates 30 days, from the start date, another 60 days, and another 90 days. Also each row needs to have an identifier that states whether the date is 30 days, 60 days or 90 days from the start date. So say that the start date is 09/01/2020. Then the View will return this for each start date:
Row Header : Start Date, AdditionalDate, AdditionalDays
Row 1 : 01/01/2020, 02/01/2020, 30
Row 2 : 01/02/2020, 03/01/2020, 60
Row 3 : 01/01/2020, 04/01/2020, 90
Sorry, forgot to mention, but start date is from a table. Like (Select startDate from Appointment)
I am using Microsoft SQL Server and a new SQL user. Really appreciate any help and advice.
Thank you!
I am unsure why what do you expect from a view for that - views don't take parameters.
Here is, however, a query that, from a given date parameter, generates three rows, at 30, 60 and 90 days later:
declare #start_date date = '2020-01-01';
select
#start_date,
dateadd(day, additional_days, #start_date) additional_date,
additional_days
from (values (30), (60), (90)) x(additional_days)
I am unsure whether you really mean 30 days or a month. If you want months, then:
declare #start_date date = '2020-01-01';
select
#start_date,
dateadd(month, additional_months, #start_date) additional_date,
additional_months
from (values (1), (2), (3)) x(additional_months)
On the other hand, if you are starting from an existing table, then that's a cross join:
select
t.*,
dateadd(day, x.additional_days, t.start_date) additional_date,
x.additional_days
from mytable t
cross join (values (30), (60), (90)) x(additional_days
You cannot use a view for this purpose, but you can use an inline table-valued function:
create function dates (
#date date,
#period int,
#num int
)
returns table
as return
with dates as (
select #date as start_date,
dateadd(day, #period, #date) as additional_date,
#period as additional_days, 1 as n
union all
select start_date,
dateadd(day, #period, additional_date),
additional_days + #period, n + 1
from dates
where n < #num
)
select start_date, additional_date, additional_days
from dates;
Here is a db<>fiddle.
You can utilize a recursive cte:
with cte as
( Select 1 as Header
,Start
,dateadd(day, 30, Start) as AdditionalDate
,30 as AdditionalDays
from Appointment
union all
select Header+1
,Start
,dateadd(day, 30, AdditionalDate)
,AdditionalDays + 30
from cte
where Header <= 2
)
Select * from cte
Or for adding months instead of days:
with cte as
( Select 1 as Header
,Start
,dateadd(month, 1, Start) as AdditionalDate
,datediff(day, Start, dateadd(month, 1, Start)) as AdditionalDays
from Appointment
union all
select Header+1
,Start
,dateadd(month, 1, AdditionalDate)
,datediff(day, Start, dateadd(month, 1, AdditionalDate))
from cte
where Header <= 2
)
Select * from cte
See fiddle

distribute accumulated working hours through days

I have Date time when engine has started working and how long was it working. but sometimes it can work more than 24 Hours.
if it worked for 28 Hours on the starting date i will have record
Name started_working Finished working hours_worked
obj-00123 07/02/2018 13:30 08/02/2018 17:30 28
I need to to have record that will show that engine has worked for 10:30 in 07 and 17:30 in 08.
Name started_working Finished working hours_worked
obj-00123 07/02/2018 13:30 07/02/2018 00:00 10:30
obj-00123 07/02/2018 13:30 08/02/2018 17:30 17:30
or something like that. I don't have any idea how can i get this done. can you give me some clues. i dont ask for writing code if its not too easy.
thank you
This might do the trick for you
--Using CTE to show sample data
;WITH cteX( Name,started_working,Finished_working)
AS
(
SELECT
'obj-00123','07/02/2018 13:30','08/02/2018 17:30' UNION ALL
SELECT 'obj-00155','07/02/2018 15:00','07/02/2018 22:30'
)
SELECT
X.Name
, X.started_working
, X.Finished_working
, HoursWorked = CONVERT(VARCHAR(12), DATEADD(minute, DATEDIFF(minute, X.started_working, X.Finished_working), 0), 114)
FROM
(
SELECT
T1.Name
,T1.started_working
,Finished_working = DATEADD(SECOND,0,DATEADD(DAY, DATEDIFF(DAY,-1,T1.started_working),0)) -- Dummy finish time # Midnight
FROM
cteX T1
WHERE
DATEDIFF(DAY,T1.started_working,T1.Finished_working) <> 0 --Create a dummy finish time #Midnight when start and end not on same day
UNION ALL
SELECT
T2.Name
,started_working = CASE WHEN DATEDIFF(DAY,T2.started_working,T2.Finished_working) <> 0
THEN DATEADD(DAY, DATEDIFF(DAY, 0, T2.Finished_working), 0) --Start # Midnight
ELSE T2.started_working
END
,T2.Finished_working
FROM
cteX T2
) X
ORDER BY
X.Name, X.started_working
OUTPUT
Name started_working Finished_working HoursWorked
obj-00123 2018-07-02 13:30:00.000 2018-07-03 00:00:00.000 10:30:00:000
obj-00123 2018-08-02 00:00:00.000 2018-08-02 17:30:00.000 17:30:00:000
obj-00155 2018-07-02 15:00:00.000 2018-07-02 22:30:00.000 07:30:00:000
According to your sample data working hours may be more than several days. In this case you need to use tally table or recursive CTE. I have used recursive CTE since it's easier to handle result fields. Also there are two columns in result named started_working and started_working2. started_working is from your expected output, but I believe you need started_working2 column
declare #T as table (
Name varchar(100)
, started_working datetime
, finished_working datetime
--, hours_worked int
)
insert into #T
values
('obj-00123', '20180207 13:30', '20180208 17:30')
, ('obj-00123', '20180208 19:00', '20180209 05:00')
, ('obj-00123', '20180209 19:00', '20180209 22:00')
, ('obj-00123', '20180210 19:00', '20180213 22:00')
;with rcte as (
select
*, started_working2 = started_working
, next_date = cast(dateadd(dd, 1, started_working) as date), 1 step
from
#T
union all
select
Name, started_working, finished_working
, cast(next_date as datetime)
, dateadd(dd, 1, next_date), step + 1
from
rcte
where
next_date < finished_working
)
select
Name, started_working, started_working2, finished_working
, right(replace(str(diff / 60), ' ', 0), 2) + ':' + right(replace(str(diff % 60), ' ', 0), 2) hours_worked
from (
select
Name, started_working
, case
when step = 1 then started_working
else started_working2
end started_working2
, case
when step = max(step) over (partition by Name, started_working)
then finished_working else next_date
end finished_working
from
rcte
) t
cross apply (select datediff(mi, started_working2, finished_working) diff) ca
I'd approach the solution something like this:
WITH dynamic_twelths_of_hr_table(datetime2_value) AS
(
SELECT '2017-01-01'
UNION ALL
SELECT DATEADD(MINUTE, 5, datetime2_value)
FROM dynamic_twelths_of_hr_table
WHERE DATEADD(MINUTE, 5, datetime2_value) <= '2019-01-01'
)
,twelths_hr_table AS
(
SELECT
DATEADD(DAY, DATEDIFF(DAY, 0, datetime2_value), 0) AS date_value
,datetime2_value
FROM dynamic_twelths_of_hr_table
)
,modified_source_table AS
(
SELECT
name
,objectid
,engine_start
,ISNULL(engine_stop, GETDATE()) AS engine_stop
,IIF(engine_start IS NULL OR engine_stop IS NULL, 1, 0) AS is_still_running
FROM [YOUR_SOURCE_TABLE]
)
SELECT
name
,objectid
,is_still_running
,date_value
,(COUNT(datetime2_value)/12.0) AS hours_run_on_this_day
FROM
modified_source_table
LEFT JOIN
twelths_hr_table AS tht
ON (tht.datetime2_value BETWEEN engine_start AND engine_stop)
GROUP BY
name, objectid, is_still_running, date_value
ORDER BY
name, objectid, is_still_running, date_value
Note I haven't tested this code so please excuse any small syntax errors.
I've also baked in an assumption about the range of dates to be considered (these can be widened, or made dynamic based on when the query runs), and it has a 5 minute resolution (based on the fact that, at a glance, I could only see one value in the engine_stop column that didn't fall on a 5-minute threshold - so I assume sub-5-minute precision is not required).
Basically what it does is expand each engine row out into 5-minute windows (twelths of an hour), and then simply groups these by day and counts the number of windows per day during which the engine was running.
For currently-running engines, it will calculate how long it has run so far. I trust you can tweak the code to your exact requirements.
thank you to all. this worked perfectly. it needed slight polishing and recursion needed to be set to 0.
But creating view is a trouble with CTE.
create view mroobjectenginerowkinghoursdeclare as
declare #T as table (
Name nvarchar(100)
, OBJECTID varchar(50)
, started_working datetime
,STOPFROM datetime
,STARTDATE datetime
,STOPDATE datetime
,MODIFIEDDATETIME datetime
,START_STOP int
,STARTDESCRIPTION nvarchar(300)
,STOPDESCRIPTION nvarchar(300)
,wattage nvarchar (50)
,purpose nvarchar(300)
,location nvarchar(300)
,finished_working datetime
,oldDiff int
)
insert into #T
select
NAME
,OBJECTID
,STOPTO
,STOPFROM
,STARTDATE
,STOPDATE
,MODIFIEDDATETIME
,START_STOP
,STARTDESCRIPTION
,STOPDESCRIPTION
,wattage
,purpose
,location
,next_stopfrom
,diff
FROM [MicrosoftDynamicsAX].[dbo].[mroobjectengineworkinghours]
;with rcte as (
select
*, started_working2 = started_working
, next_date = cast(dateadd(dd, 1, started_working) as date), 1 step
from
#T
union all
select
Name,OBJECTID, started_working,STOPFROM,STARTDATE,STOPDATE,MODIFIEDDATETIME,START_STOP,STARTDESCRIPTION
,STOPDESCRIPTION,wattage
,purpose
,location, finished_working,oldDiff
, cast(next_date as datetime)
, dateadd(dd, 1, next_date), step + 1
from
rcte
where
next_date < finished_working
)
select
Name,OBJECTID, started_working,STOPFROM,STARTDATE,STOPDATE,MODIFIEDDATETIME,START_STOP,STARTDESCRIPTION
,STOPDESCRIPTION,wattage
,purpose
,location,oldDiff, started_working2, finished_working
, right(replace(str(diff / 60), ' ', 0), 2) + ':' + right(replace(str(diff % 60), ' ', 0), 2) hours_worked
from (
select
Name,OBJECTID, started_working,STOPFROM,STARTDATE,STOPDATE,MODIFIEDDATETIME,START_STOP,STARTDESCRIPTION
,STOPDESCRIPTION,wattage
,purpose
,location,oldDiff
, case
when step = 1 then started_working
else started_working2
end started_working2
, case
when step = max(step) over (partition by Name, started_working)
then finished_working else next_date
end finished_working
from
rcte
) t
cross apply (select datediff(mi, started_working2, finished_working) diff) ca
OPTION (MAXRECURSION 0);

Simplest way to fill working day table

I have a table working_days with one column date of type date
I need to fill it with working days in USA.
Can you suggest how can I so this?
Manually it is too long.
You can use a recursive CTE to accomplish this. This only excludes the weekends. Using DATEFIRST you can figure out what day is a weekend. This query should work no matter what day of the week is set to DATEFIRST.
;WITH DatesCTE
AS (
SELECT CAST('2016-01-01' AS DATE) AS [workingDays]
UNION ALL
SELECT DATEADD(DAY, 1, workingdays)
FROM DatesCTE
WHERE DATEADD(DAY, 1, workingdays) < '2017-01-01'
)
SELECT *
FROM DatesCTE
WHERE ((DATEPART(dw, workingDays) + ##DATEFIRST) % 7) NOT IN (0, 1)
OPTION (MAXRECURSION 366)
At first fill your table with all dates for year (for example 2016):
DECLARE #date_start date = '2016-01-01',
#date_end date = '2016-12-31';
WITH cte as (
SELECT #date_start as [d], 0 as Level
UNION ALL
SELECT DATEADD(day,1,[d]), [level] + 1 as [level]
from cte
WHERE [level] < DATEDIFF(day,#date_start,#date_end)
),
holidays as ( --table with holidays
SELECT * FROM (VALUES
('2016-01-01'),
('2016-01-18'),
('2016-02-15'),
('2016-05-30'),
('2016-07-04'),
('2016-09-05'),
('2016-10-10'),
('2016-11-11'),
('2016-11-24'),
('2016-12-26')) as t(d)
)
SELECT c.d
FROM cte c
LEFT JOIN holidays h on c.d=h.d
WHERE DATEPART(WEEKDAY,d) NOT IN (1,7) --will show only monday-friday
AND AND h.d is NULL
OPTION (MAXRECURSION 1000); --if you need more than 3 years get MAXRECURSION up
A simple loop will do:
declare #d date = '20160101';
while #d <= '20161231'
begin
if datepart(weekday, #d) not in (1, 7) and <#d not a holiday>
insert into working_days ("date") values (#d);
set #d = dateadd(day, 1, #d);
end

Average per quarter hour

I have a table with these columns:
Id, Method, DateTime, time taken
Ex
1, Done, 2014-06-22 08:18:00.000, 2000
2, Not Done, 2014-06-23 04:15:00.000, 5000
3, Done, 2014-06-23 14:15:00.000, 6000
I want to have a result set as, "average time taken by DONE methods in each 15 min interval between 8AM to 15PM"
Please guide me on how to proceed on this, I am not sure if cursor fits in this req.
You can use a CTE to generate a list of quarters. Then left join to look up the run times per quarter. A group by will allow you to calculate the average.
In SQL Server 2012, the time type is available, and you can:
; with quarters as
(
select cast('08:00' as time) as time
union all
select dateadd(minute, 15, time)
from quarters
where time <= '14:30'
)
select q.time
, avg(rt.time_taken) as avg_time_taken
from quarters q
left join
RunTime rt
on q.time <= cast(rt.dt as time)
and cast(rt.dt as time) < dateadd(minute, 15, q.time)
and method = 'Done'
group by
q.time
Live example at SQL Fiddle.
For SQL Server 2008R2 and earler, you can use integer math instead:
; with quarters as
(
select 8*60 as min
union all
select min + 15
from quarters
where min < 15*60
)
select q.min / 60 as hour
, q.min % 60 as minute
, avg(rt.time_taken) as avg_time_taken
from quarters q
left join
(
select datepart(minute, dt) +
60 * datepart(hour, dt) as min
, time_taken
from RunTime
where method = 'Done'
) rt
on q.min <= rt.min and rt.min < q.min + 15
group by
q.min;
Live example at SQL Fiddle.
I'm not entirely sure if this is what you want, but here ist the code:
CREATE TABLE #Test(
id int IDENTITY(1,1) PRIMARY KEY,
Method nvarchar(50),
[Datetime] datetime,
timeTaken Bigint
)
CREATE TABLE #Result(
[Between] datetime,
[And] datetime,
[Avg] bigint)
INSERT INTO #Test (Method,Datetime,timeTaken)
VALUES(
'Done', '2014-06-22 08:18:00.000', 2000),
('Not Done', '2014-06-23 04:15:00.000', 5000),
('Done', '2014-06-23 14:15:00.000', 6000)
DECLARE #MaxTime datetime,#StartTime datetime,#Next datetime
SELECT #MaxTime = MAX([datetime]),
#StartTime = MIN([datetime])
FROM #TEST
WHILE #StartTime <= #MaxTime
BEGIN
SET #Next = (SELECT Dateadd(MINUTE,15,#StartTime))
INSERT INTO #Result
SELECT #StartTime AS [Between], #Next AS [And],AVG(timeTaken) AS [AVG]
FROM #Test
WHERE [Datetime] Between #StartTime AND #Next
AND Method = 'Done'
SET #StartTime = #Next
END
SELECT * FROM #Result
DROP TABLE #Test
DROP TABLE #Result
You can now set a where to the Select * from #result in which you can say between 8 AM and 3 PM
Please let me know if this is what you want
Etienne

How I can select / sort dates by period intervals?

For ex:
If we have in table records like:
25/06/2009
28/12/2009
19/02/2010
16/04/2011
20/05/2012
I want to split/select this dates according to 6 month intervals starting from current date.
result should be like:
0-6 month from now: first record
7-12 month from now: second record
...
It will be much apreciated if you make this simple as I made it very stupid and complicated like:
declare variable like t1=curdate()+6
t2=curdate()+12
...
then selected records to fit between curdate() and t1, then t1 and t2 etc.
Thanks,
r.
CORRECTION: Had it backwards, Need to use Modulus, not integer division - sorry...
If MonthCount is a calculated value which counts the number of months since a specific Dec 31, and mod is modulus division (output the remainder after dividing)
Select [Column list here]
From Table
Group By Case When MonthCount Mod 12 < 6
Then 0 Else 1 End
In SQL Server, for example, you could use the DateDiff Function
Select [Column list here]
From Table
Group By Case When DateDiff(month, myDateColumn, curdate) % 12 < 6
Then 0 Else 1 End
( in SQL Server the percent sign is the modulus operator )
This will group all the record into buckets which each contain six months of data
SELECT (DATEDIFF(MONTH, thedate, GETDATE()) / 6) AS semester,
SUM(receipt)
FROM thetable
GROUP BY semester
ORDER BY semester
the key idea is grouping and ordering by the expression that gives you the "semester".
This question really baffled me, cos I couldn't actually come up with a simple solution for it. Damn.
Best I could manage was an absolute bastardization of the following where you create a Temp Table, insert the "Periods" into it, join back to your original table, and group off that.
Assume your content table has the following
ID int
Date DateTime
Counter int
And you're trying to sum all the counter's in six month periods
DECLARE #min_date datetime
select #min_date = min(date) from test
DECLARE #max_date datetime
select #max_date = max(date) from test
DECLARE #today_a datetime
DECLARE #today_b datetime
set #today_a = getdate()
set #today_b = getdate()
CREATE TABLE #temp (startdate DateTime, enddate DateTime)
WHILE #today_a > #min_date
BEGIN
INSERT INTO #temp (startDate, endDate) VALUES (dateadd(month, -6, #today_a), #today_a)
SET #today_a = dateadd(month, -6, #today_a)
END
WHILE #today_b < #max_date
BEGIN
INSERT INTO #temp (startDate, endDate) VALUES (#today_b, dateadd(month, 6, #today_b))
SET #today_b = dateadd(month, 6, #today_b)
END
SELECT * FROM #temp
SELECT
sum(counter),
'Between ' + Convert(nvarchar(10), startdate, 121) + ' => ' + Convert(nvarchar(10), enddate, 121) as Period
FROM test t
JOIN #Temp ht
ON t.Date between ht.startDate AND ht.EndDate
GROUP BY
'Between ' + Convert(nvarchar(10), startdate, 121) + ' => ' + Convert(nvarchar(10), enddate, 121)
DROP TABLE #temp
I really hope someone can come up with a better solution my brain has obviously melted.
Not quite what you're attempting to accomplish, but you could use the DATEDIFF function to distinguish the ranging of each record:
SELECT t.MonthGroup, SUM(t.Counter) AS TotalCount
FROM (
SELECT Counter, (DATEDIFF(m, GETDATE(), Date) / 6) AS MonthGroup
FROM Table
) t
GROUP BY t.MonthGroup
This would create a sub query with an expression that expresses the date ranging group you want. It would then group the sub-query by this date ranging group and you can then do whatever you want with the results.
Edit: I modified the example based on your example.
If you're using SQL Server:
SELECT *,
(
FLOOR
(
(
DATEDIFF(month, GETDATE(), date_column)
- CASE WHEN DAY(GETDATE()) > DAY(date_column) THEN 1 ELSE 0 END
) / 6.0
) * 6
) AS SixMonthlyInterval
FROM your_table
If you're using MySQL:
SELECT *,
(
FLOOR
(
(
((YEAR(date_column) - YEAR(CURDATE())) * 12)
+ MONTH(date_column) - MONTH(CURDATE())
- CASE WHEN DAY(CURDATE()) > DAY(date_column) THEN 1 ELSE 0 END
) / 6.0
) * 6
) AS SixMonthlyInterval
FROM your_table