MySQL group by intervals in a date range - sql

I am going to be graphing netflow data stored in a MySQL database, and I need an efficient way to get the relevant data points. They records are stored with the date as an int for seconds since epoch. I Would like to be able to something like:
Select SUM(bytes) from table where stime > x and stime < Y
group by (10 second intervals)
Is there anyway to do this? or, would it be faster to handle it locally in python? even for a 500K row table?
EDIT
My Mistake, the time is stored as an unsigned double instead of an INT.
I'm currently using GROUP BY (FLOOR(stime / I)) where I is the desired interval.

You may be able to do this using integer division. Not sure of the performance.
Let I be your desired interval in seconds.
SELECT SUM(bytes), ((stime - X) DIV I) as interval
FROM table
WHERE (stime > X) and (stime < Y)
GROUP BY interval
Example, let X = 1500 and I = 10
stime = 1503 -> (1503 - 1500) DIV 10 = 0
stime = 1507 -> (1507 - 1500) DIV 10 = 0
stime = 1514 -> (1514 - 1500) DIV 10 = 1
stime = 1523 -> (1523 - 1500) DIV 10 = 2

Have you tried the following? Just devide the tyiem column by 10 and round the result down.
SELECT SUM(bytes)
FROM table
WHERE stime > x
AND stime < Y
GROUP BY ROUND(stime/10, -1)
I don't know wether the ROUND() function and grouping with function calls works in MySQL though, the above is T-SQL.

FLOOR in group by sometimes fails. it sometimes groups different times as one value for example when you divide the value with 3 but it doesn't do the same when you divide with 4, although the difference between these two values is far bigger than 3 or 4 which it should group as two different groups. Better cast it to unsigned after floor which works like:
CAST(FLOOR(UNIX_TIMESTAMP(time_field)/I) AS UNSIGNED INT)
The problem:
Sometimes GROUP BY FLOOR(UNIX_TIMESTAMP(time_field)/3) gives less groups compared to GROUP BY FLOOR(UNIX_TIMESTAMP(time_field)/4) which is mathematically shouldn't be possible.

SELECT sec_to_time(time_to_sec(datefield)- time_to_sec(datefield)%(10)) as intervals,SUM(bytes)
FROM table
WHERE where stime > x and stime < Y
group by intervals

I used suggestions from both answers and a coworker. End result is as follows:
Select FROM_UNIXTIME(stime), bytes
from argusTable_2009_10_22
where stime > (UNIX_TIMESTAMP()-600)
group by floor(stime /10)
I tried the rounding solution as well, but the results were inconsistent.
Chance

I did this a few time ago, so i created some function (with sql server, but i assume it's nearly the same) :
First I created a scalar function that return me the ID of a date depending on an interval and a date part (minute,hour,day,moth,year):
CREATE FUNCTION [dbo].[GetIDDate]
(
#date datetime,
#part nvarchar(10),
#intervalle int
)
RETURNS int
AS
BEGIN
-- Declare the return variable here
DECLARE #res int
DECLARE #date_base datetime
SET #date_base = convert(datetime,'01/01/1970',103)
set #res = case #part
WHEN 'minute' THEN datediff(minute,#date_base,#date)/#intervalle
WHEN 'hour' THEN datediff(hour,#date_base,#date)/#intervalle
WHEN 'day' THEN datediff(day,#date_base,#date)/#intervalle
WHEN 'month' THEN datediff(month,#date_base,#date)/#intervalle
WHEN 'year' THEN datediff(year,#date_base,#date)/#intervalle
ELSE datediff(minute,#date_base,#date)/#intervalle END
-- Return the result of the function
RETURN #res
END
Then I created a table function that returns me all the id betweend a date range :
CREATE FUNCTION [dbo].[GetTableDate]
(
-- Add the parameters for the function here
#start_date datetime,
#end_date datetime,
#interval int,
#unite varchar(10)
)
RETURNS #res TABLE (StartDate datetime,TxtStartDate nvarchar(50),EndDate datetime,TxtEndDate nvarchar(50),IdDate int)
AS
begin
declare #current_date datetime
declare #end_date_courante datetime
declare #txt_start_date nvarchar(50)
declare #txt_end_date nvarchar(50)
set #current_date = case #unite
WHEN 'minute' THEN dateadd(minute, datediff(minute,0,#start_date),0)
WHEN 'hour' THEN dateadd(hour, datediff(hour,0,#start_date),0)
WHEN 'day' THEN dateadd(day, datediff(day,0,#start_date),0)
WHEN 'month' THEN dateadd(month, datediff(month,0,#start_date),0)
WHEN 'year' THEN dateadd(year, datediff(year,0,dateadd(year,#interval,#start_date)),0)
ELSE dateadd(minute, datediff(minute,0,#start_date),0) END
while #current_date < #end_date
begin
set #end_date_courante =
case #unite
WHEN 'minute' THEN dateadd(minute, datediff(minute,0,dateadd(minute,#interval,#current_date)),0)
WHEN 'hour' THEN dateadd(hour, datediff(hour,0,dateadd(hour,#interval,#current_date)),0)
WHEN 'day' THEN dateadd(day, datediff(day,0,dateadd(day,#interval,#current_date)),0)
WHEN 'month' THEN dateadd(month, datediff(month,0,dateadd(month,#interval,#current_date)),0)
WHEN 'year' THEN dateadd(year, datediff(year,0,dateadd(year,#interval,#current_date)),0)
ELSE dateadd(minute, datediff(minute,0,dateadd(minute,#interval,#current_date)),0) END
SET #txt_start_date = case #unite
WHEN 'minute' THEN CONVERT(VARCHAR(20), #current_date, 100)
WHEN 'hour' THEN CONVERT(VARCHAR(20), #current_date, 100)
WHEN 'day' THEN REPLACE(CONVERT(VARCHAR(11), #current_date, 106), ' ', '-')
WHEN 'month' THEN REPLACE(RIGHT(CONVERT(VARCHAR(11), #current_date, 106), 8), ' ', '-')
WHEN 'year' THEN CONVERT(VARCHAR(20), datepart(year,#current_date))
ELSE CONVERT(VARCHAR(20), #current_date, 100) END
SET #txt_end_date = case #unite
WHEN 'minute' THEN CONVERT(VARCHAR(20), #end_date_courante, 100)
WHEN 'hour' THEN CONVERT(VARCHAR(20), #end_date_courante, 100)
WHEN 'day' THEN REPLACE(CONVERT(VARCHAR(11), #end_date_courante, 106), ' ', '-')
WHEN 'month' THEN REPLACE(RIGHT(CONVERT(VARCHAR(11), #end_date_courante, 106), 8), ' ', '-')
WHEN 'year' THEN CONVERT(VARCHAR(20), datepart(year,#end_date_courante))
ELSE CONVERT(VARCHAR(20), #end_date_courante, 100) END
INSERT INTO #res (
StartDate,
EndDate,
TxtStartDate,
TxtEndDate,
IdDate) values(
#current_date,
#end_date_courante,
#txt_start_date,
#txt_end_date,
dbo.GetIDDate(#current_date,#unite,#interval)
)
set #current_date = #end_date_courante
end
return
end
So if I want to count all the user added for each interval of 33 minutes :
SELECT count(id_user) , timeTable.StartDate
FROM user
INNER JOIn dbo.[GetTableDate]('1970-01-01',datedate(),33,'minute') as timeTable
ON dbo.getIDDate(user.creation_date,'minute',33) = timeTable.IDDate
GROUP BY dbo.getIDDate(user.creation_date,'minute',33)
ORDER BY timeTable.StartDate
:)

Related

Difficulty in getting Shift Value in SQL Query

Currently my table structire for table SHIFT is as follow:
ID Name Start End
1 Shift1 06:00 14:00
2 Shift2 14:00 22:00
3 Shift3 22:00 06:00
Now I pass parameter to this query in hour like 11 or 15 or 22 or 03
For that parameter, I would like to get the result that in which shift the passed hour will reside.
So if I pass 11, it shoud give me Shift1. If I pass 23, it should give me Shift3.
Following query that I wrote works fine for any value from 07 to 21, it is giving me blank value and for obvious reasons.
select * from MII_SHIFT
where '[Param.1]' >= left(START,2) and '[Param.1]' < left(END,2)
Can anyone help me how can I change the query so that I can get proper response for 22,23,00,01,02,03,04,05.
Thanks
SELECT *
FROM shift
WHERE
( left(START,2) > left(END,2)
AND ('[Param.1]' >= left(START,2) OR '[Param.1]' < left(END,2))
)
OR ( left(START,2) < left(END,2)
AND '[Param.1]' >= left(START,2) AND '[Param.1]' < left(END,2)
)
I answer a similar answer a litle time ago.
Shorts start < end (5-9): the value need be between start and end
Jacket start > end (10-4): the value is < start or > end
Assuming the values are stored as strings, then this is pretty easy:
select s.*
from shifts s
where (start < end and right('00' + #param1, 2) >= start and right('00' + #param1, 2) < end) or
(start > end and (right('00' + #param1, 2) >= start or right('00' + #param1, 2) < end))
This assumes that #param1 is a string. The right() is used to left pad the string with zeroes. If that is already true, then the code would be even simpler.
EDIT:
With padding, this simplifies to:
select s.*
from shifts s
where (start < end and #param1 >= start and #param1< end) or
(start > end and (#param1 >= start or #param1 < end))
Simplest way is most likely to convert the times into dates, and if the end date is earlier than start, then add one day. You could use time datatype as input too, instead of just hour, but this is now an example with int:
declare #hour int, #date datetime
set #hour = 3
set #date = convert(datetime, convert(varchar(2), #hour) + ':00', 108)
select Name
from (
select Name,
[Start] as Start1,
case when [End] < [Start] then dateadd(day, 1, [End]) else [End] End as End1,
case when [End] < [Start] then dateadd(day, -1, [Start]) else [Start] End as Start2,
[End] as End2
from (
select Name, convert(datetime, [Start], 108) as [Start], convert(datetime, [End], 108) as [End]
from Table1
) X
) Y
where ((Start1 <= #date and #date < End1) or (Start2 <= #date and #date < End2))
Edit: added 2nd start / end columns to the derived table to handle second part of the shift.
Example in SQL Fiddle
Thankk you all. With the hep from all of your refrences, I was able to build the query which gave me appropriate results.
Query is as foolow:
SELECT Name FROM SHIFT WHERE
(LEFT(START,2) < LEFT(END,2) AND '[Param.1]' >= LEFT(START,2) AND '[Param.1]' < LEFT(END,2))
OR
(LEFT(START,2) > LEFT(END,2) AND ('[Param.1]' >= LEFT(START,2) OR '[Param.1]' < LEFT(END,2)))

How To Get Elapsed Time Between Two Dates, Ignoring Some Time Range?

I have a table with a DATETIME column 'Start' and a DATETIME column 'End'. I want to return the number of minutes between the start and the end (End is always after than Start). Usually I'd just use 'DateDiff()' but this time I need to exclude another date range. For example - From Tuesday at 9am until Wednesday at 6pm, of each week, should be ignored.
If a row has a Start of Tuesday at 8am and an End of Wednesday at 7pm - the elapsed time should be two hours (120 minutes) - because of the ignored date range.
I'm having trouble coming up with a decent way of doing this and my searching online hasn't found quite what I'm looking for. Can someone help me along?
Try This:
--total time span to calculate difference
DECLARE #StartDate DATETIME = '2015-11-10 8:00:00AM',
#EndDate DATETIME = '2015-11-11 7:00:00PM'
--get the day of week (-1 because sunday is counted as first weekday)
DECLARE #StartDayOfWeek INT = (SELECT DATEPART(WEEKDAY, #StartDate)) -1
DECLARE #EndDayOfWeek INT = (SELECT DATEPART(WEEKDAY, #EndDate)) -1
--set the time span to exclude
DECLARE #InitialDOWToExclude TINYINT = 2
DECLARE #InitialTODToExclude VARCHAR(100) = '9:00:00 AM'
DECLARE #EndDOWToExclude TINYINT = 3
DECLARE #EndTODToExclude VARCHAR(100) = '6:00:00 PM'
--this will be the final output in hours
DECLARE #ElapsedHours INT = (SELECT DATEDIFF(HOUR, #StartDate, #EndDate))
DECLARE #WeeksBetween INT = (SELECT DATEDIFF(WEEK, #StartDate, #EndDate))
DECLARE #Iterator INT = 0
WHILE (#Iterator <= #WeeksBetween)
BEGIN
DECLARE #InitialDaysBetween INT = #StartDayOfWeek - #InitialDOWToExclude
DECLARE #StartDateToExclude DATETIME = (SELECT DATEADD(DAY, #InitialDaysBetween, DATEADD(WEEK, #Iterator, #StartDate)))
SET #StartDateToExclude =CAST(DATEPART(YEAR, #StartDateToExclude) AS VARCHAR(100))
+ CAST(DATEPART(MONTH, #StartDateToExclude) AS VARCHAR(100))
+ CAST(DATEPART(DAY, #StartDateToExclude) AS VARCHAR(100))
+ ' '
+ CAST(#InitialTODToExclude AS VARCHAR(100))
DECLARE #EndDaysBetween INT = #EndDayOfWeek - #EndDOWToExclude
DECLARE #EndDateToExclude DATETIME = (SELECT DATEADD(DAY, #EndDaysBetween, DATEADD(WEEK, #Iterator, #EndDate)))
SET #EndDateToExclude =CAST(DATEPART(YEAR, #EndDateToExclude) AS VARCHAR(100))
+ CAST(DATEPART(MONTH, #EndDateToExclude) AS VARCHAR(100))
+ CAST(DATEPART(DAY, #EndDateToExclude) AS VARCHAR(100))
+ ' '
+ CAST(#EndTODToExclude AS VARCHAR(100))
SET #ElapsedHours = #ElapsedHours - DATEDIFF(HOUR, #StartDateToExclude, #EndDateToExclude)
SET #Iterator = #Iterator + 1
END
SELECT #ElapsedHours
This might get you pretty close..
DECLARE #Table1 TABLE ([Id] INT, [Start] DATETIME, [End] DATETIME)
INSERT INTO #Table1 VALUES
(1, '2015-11-08 00:00:00', '2015-11-10 21:45:38'),
(2, '2015-11-09 00:00:00', '2015-11-11 21:45:38')
;
-- hours to exclude
WITH excludeCTE AS
(
SELECT *
FROM (VALUES('Tuesday', 9, 0), ('Wednesday', 0, 0)) AS T([Day], [Hour], [Amount])
UNION ALL
SELECT [Day], [Hour] + 1, [Amount]
FROM excludeCTE
WHERE ([Day] = 'Tuesday' AND [Hour] < 23) OR ([Day] = 'Wednesday' AND [Hour] < 18)
),
-- all hours between start and end
dateCTE AS
(
SELECT [Id],
[Start],
[End],
DATENAME(weekday, [Start])[Day],
DATENAME(hour, [Start])[Hour]
FROM #Table1 t
UNION ALL
SELECT cte.[Id],
DATEADD(HOUR, 1, cte.[Start]),
cte.[End],
DATENAME(weekday, DATEADD(HOUR, 1, cte.[Start]))[Day],
DATENAME(hour, DATEADD(HOUR, 1, cte.[Start]))[Hour]
FROM #Table1 t
JOIN dateCTE cte ON t.Id = cte.Id
WHERE DATEADD(HOUR, 1, cte.[Start]) <= t.[End]
)
SELECT t.[Id],
t.[Start],
t.[End],
SUM(COALESCE(e.[Amount], 1)) [Hours]
FROM #Table1 t
INNER JOIN dateCTE d ON t.[Id] = d.[Id]
LEFT JOIN excludeCTE e ON d.[Day] = e.[Day] AND d.[Hour] = e.[Hour]
GROUP BY t.[Id],
t.[Start],
t.[End]
OPTION (MAXRECURSION 0) -- allow more than 100 hours
Putting the additional constraint that there can only be one excluded range between any two date
CREATE TABLE worktable (
_Id INT
, _Start DATETIME
, _End DATETIME
);
INSERT INTO worktable VALUES
(1, '2015-11-09 00:00:00', '2015-11-09 00:45:00') -- Start and End before excluded range
, (2, '2015-11-09 00:00:00', '2015-11-11 21:45:00') -- Start before, End after
, (3, '2015-11-09 00:00:00', '2015-11-10 21:00:00') -- Start before, End between
, (4, '2015-11-10 10:00:00', '2015-11-11 10:00:00') -- Start between, End between
, (5, '2015-11-10 10:00:00', '2015-11-11 21:45:00') -- Start between, End after
With getDates As (
SELECT _Id
, a = _Start
, b = _End
, c = DATEADD(hh, 9
, DATEADD(DAY,DATEDIFF(DAY, 0, _Start) / 7 * 7
+ 7 * Cast(Sign(1 - DatePart(dw, _Start)) + 1 as bit), 1))
, d = DATEADD(hh, 18
, DATEADD(DAY,DATEDIFF(DAY, 0, _Start) / 7 * 7
+ 7 * Cast(Sign(1 - DatePart(dw, _Start)) + 1 as bit), 2))
FROM worktable
), getDiff As (
SELECT c_a = DATEDIFF(mi, a, c)
, c_b = DATEDIFF(mi, b, c)
, b_d = DATEDIFF(mi, d, b)
, a, b, c, d, _id
FROM getDates
)
Select _id
, (c_a + ABS(c_a)) / 2
- (c_b + ABS(c_b)) / 2
+ (b_d + ABS(b_d)) / 2
FROM getDiff;
c is the date of the first Tuesday after the start date (Find the next occurance of a day of the week in SQL) you may need to adjust the last value depending from DATEFIRST
d is the date of the first Wednesday after the start date in the same week of c
Cast(Sign(a - b) + 1 as bit) is 1 if a is more than or equal b, 0 otherwise
(x + ABS(x)) / 2 is x if not negative, otherwise 0
Given that the formula to get the elapsed time with the excluded range is:
+ (Exclusion Start - Start) If (Start < Exclusion Start)
- (Exclusion Start - End) If (End < Exclusion Start)
+ (End - Exclusion End) If (Exclusion End < End)
-- excluded range (weekday numbers run from 1 to 7)
declare #x datetime = /*ignore*/ '1900012' + /*start day # and time*/ '3 09:00am';
declare #y datetime = /*ignore*/ '1900012' + /* end day # and time*/ '4 06:00pm';
-- normalize date to 1900-01-21, which was a Sunday
declare #s datetime =
dateadd(day, 19 + datepart(weekday, #start), cast(cast(#start as time) as datetime));
declare #e datetime =
dateadd(day, 19 + datepart(weekday, #end), cast(cast(#end as time) as datetime));
-- split range into two parts, one before #x and the other after #y
-- each part collapses to zero when #s and #e respectively fall between #x and #y
select (
datediff(second, -- diff in minutes would truncate so count seconds
case when #s < #x then #s else #x end, -- minimum of #s, #x
case when #e < #x then #e else #x end -- minimum of #e, #x
) +
datediff(second,
case when #s > #y then #s else #y end, -- maximum of #s, #y
case when #e > #y then #e else #y end -- maximum of #e, #y
)
) / 60; -- convert seconds to minutes, truncating with integer division
I glanced at the earlier answers and I thought that surely there was something more straightforward and elegant. Perhaps this is easier to understand and one clear advantage over some solutions is that it's trivial to change the excluded range and that range doesn't have to be limited to a single day.
I'm assuming that your dates never span more than one regular calendar week. It wouldn't be too difficult to extend it to handle more though. One approach would be to handle starting and ending partial weeks plus the full weeks in the middle.
Imagine that your start time is 8:59:30am and your end time is 6:00:30pm. In such a case I'm figuring that you'd want to accumulate the half minutes on each side to get a full minute in total after subtracting the 9-6 block. If you use datediff(minute, ...) you would be truncating the partial minutes and never get the chance to add them together: so that's why I count seconds and then divide by sixty at the end. Of course, if you're only dealing in whole minutes then you won't need to do it that way.
I've chosen my reference date somewhat arbitrarily. At first I thought it might possibly be handy to look at a real and convenient date on the calendar but ultimately it only really matters that it falls on a Sunday. So I settled on the first Sunday falling on a date ending in the digit 1.
Note that the solution also relies on datefirst being set to Sunday. That could be tweaked or made more portable if necessary.

TSQL Averaging over datepart

I have a table with a datetime column in it, consider it an event log for simple, analogous purposes.
I want to produce a report detailing the average number of events that occur at each time of day, to 30 min accuracy.
so the logic is,
get just the time component of each date
round the time to the nearest 30 min window (it can be floored, i.e. 00:29 -> 00:00)
count these (grouped by date)
average all these counts over all days
I also don't want to have any time holes in my data, for example, if nothing occurred in the 00:00 - 00:30 range, i want to report a 0, rather than having a missing row.
How can I achieve this?
WITH TestDates (date) AS (
SELECT CONVERT(DATETIME, '2011-11-15 10:00') UNION ALL
SELECT CONVERT(DATETIME, '2011-11-15 11:31') UNION ALL
SELECT CONVERT(DATETIME, '2011-11-16 10:00')
-- CTE to generate 4 million rows with a sequential integer starting at 0
), GeneratedRows (seq) AS (
SELECT ROW_NUMBER() OVER (ORDER BY N1.number) - 1
FROM master..spt_values AS N1
CROSS JOIN master..spt_values AS N2
WHERE N1.name IS NULL
AND N2.name IS NULL
), RoundedTestDates (date) AS (
SELECT CASE
-- Subtract the minute part
WHEN DATEPART(MINUTE, date) < 25 THEN DATEADD(MINUTE, -1 * DATEPART(MINUTE, date), date)
-- Subtract the minute part, then add an hour
WHEN DATEPART(MINUTE, date) >= 45 THEN DATEADD(HOUR, 1, DATEADD(MINUTE, -1 * DATEPART(MINUTE, date), date))
-- Subtract the minute part, then add an half-hour
ELSE DATEADD(MINUTE, 30, DATEADD(MINUTE, -1 * DATEPART(MINUTE, date), date))
END
FROM TestDates
)
SELECT rounded_date = GeneratedPeriod.date
, ocurrences = COUNT(RoundedTestDates.date)
FROM (SELECT DATEADD(MINUTE, 30 * seq, (SELECT MIN(date) FROM RoundedTestDates))
FROM GeneratedRows
) AS GeneratedPeriod (date)
LEFT JOIN RoundedTestDates
ON GeneratedPeriod.date = RoundedTestDates.date
WHERE GeneratedPeriod.date <= (SELECT MAX(date) FROM RoundedTestDates)
GROUP BY GeneratedPeriod.date
ORDER BY 1
Here is the code you need: (tested in sql2008 and works fine!)
-- Table with the 48 30mins periods of the day
CREATE TABLE #Periods
(
Num INT
)
DECLARE #idt INT
SET #idt = 1
WHILE (#idt <= 48)
BEGIN
INSERT INTO #Periods VALUES (#idt)
SET #idt = #idt + 1
END
--Average of the count for each period on all days.
SELECT DayTable.Num, AVG(CAST(DayTable.DayCount AS DECIMAL))
FROM
( --Total incidents for each interval on each day.
SELECT CAST(FLOOR(CAST(#MyLog.LogDate AS FLOAT)) AS DATETIME) AS DayWithOutTime,
#Periods.Num AS Num,
COUNT(#MyLog.ID) AS DayCount
FROM #Periods LEFT JOIN #MyLog
ON #Periods.Num = (DATEPART(hh, #MyLog.LogDate)*60 + DATEPART(mi,#MyLog.LogDate))/30
GROUP BY CAST(FLOOR(CAST(#MyLog.LogDate AS FLOAT)) AS DATETIME),
#Periods.Num
) AS DayTable
GROUP BY DayTable.Num
DROP TABLE #Periods
Where #NyLog is the table where your datetime is. It shows the count of incidences for each 30min period. The Period 1 is 00:00 -> 00:30 and Period 48 is 23:30 -> 24:00.
In sybase sql is something like this, in sql-server you might need to do some changes but not much :)
create procedure Test #startDay varchar(8), #endDay varchar(8)
as
declare #ocurrence int
declare #numberOfDays int
select #numberOfDays = 0
create table #intervals (
interval_hour int,
interval_min_minute int,
interval_max_minute int,
ocurrences int
)
create table #insertions (
hour int,
minute int
)
declare #hour int, #minute int
select #hour = 0
-- create the intervals
while (#hour <> 24)
begin
insert into #intervals values(#hour,0,29,0)
insert into #intervals values(#hour,30,59,0)
select #hour = #hour + 1
end
while(#startDay <> #endDay)
begin
insert into #insertions
select datepart(hh, *yourcolumn*), datepart(mm, *yourcolumn*) from *yourdb..yourtable* where convert(varchar(8), *yourcolumn*, 112) = #startDay
select #startDay = convert(varchar(8), dateadd(dd, 1, convert(datetime, #startDay, 112)), 112)
select #numberOfDays = #numberOfDays + 1
end
declare cursor1 cursor for
select hour, minute from #insertions
open cursor1
fetch cursor1 into #hour, #minute
while (##sqlstatus=0)
begin
update #intervals
set i.ocurrences = i.ocurrences + 1
from #intervals i
where interval_hour = #hour and #minute between interval_min_minute and interval_max_minute
fetch cursor1 into #hour, #minute
end
close cursor1
select interval_hour 'hour', interval_min_minute 'min minute', interval_max_minute 'max minute', ocurrences,
case when ocurrences > 0 then convert(float, ocurrences) / convert(float, #numberOfDays) else 0 end 'ocurrences average' from #intervals
drop table #intervals
drop table #insertions
go
What I've done is use an auxiliary table of numbers (a 1 column table with number 1 to 1 million) and join to it, adding the value of the number with the dateadd function to the midnight of the date.
since you want 30 minute intervals, then you want to use the dateadd(minute, number*30, yourdate) where number <= 48 (since there are 1440 minutes in a day)/30 = 48 intervals. This will create your time intervals.
Then simply count your occurrences that happen in between the time intervals.

t-sql function to determine someone category

If I am given a date like 1999-07-08 15:49:00 what would be a good function to determine whether is an AM shift, PM shift or a NOC shift?
--AM: 06:45:00 - 14:44:59
--PM: 14:45:00 - 22:59:59
--NOC: 23:00:00 - 06:44:59
Here is my attempt but then I noticed a bug
ALTER FUNCTION [dbo].[DateToNocShift]
(
-- Add the parameters for the function here
#DummyDate DATETIME
)
RETURNS VARCHAR(10)
AS
BEGIN
-- Declare the return variable here
DECLARE #Shift VARCHAR(10)
DECLARE #DateValues TABLE
(
RawDate DATETIME,
HourNow int,
MinuteNow int,
TimeHourMinute FLOAT,
Shift VARCHAR(4)
)
INSERT INTO #DateValues
VALUES
(
#DummyDate,
DATEPART(hour,#DummyDate),
cast(DATEPART(minute,#DummyDate)as decimal),
ROUND(DATEPART(hour,#DummyDate) + cast(DATEPART(minute,#DummyDate)as decimal)/60,2),
null
)
UPDATE #DateValues
SET Shift = 'AM'
WHERE TimeHourMinute BETWEEN 6.75 AND 14.74 -- good estimate
UPDATE #DateValues
SET Shift = 'PM'
WHERE TimeHourMinute BETWEEN 14.75 AND 22.99
UPDATE #DateValues
SET Shift = 'NOC'
WHERE TimeHourMinute BETWEEN 23.00 AND 6.74
SELECT #Shift = Shift FROM #DateValues
RETURN #Shift
You don't need so much code, a single CASE statement will do
CREATE FUNCTION [dbo].[DateToNocShift]
(
-- Add the parameters for the function here
#DummyDate DATETIME
)
RETURNS VARCHAR(10)
AS
BEGIN
DECLARE #dateChar varchar(8)
set #dateChar = convert(varchar, #DummyDate, 108)
RETURN CASE
WHEN #dateChar >= '06:45:00' and #dateChar < '14:45:00' then 'AM'
WHEN #dateChar >= '14:45:00' and #dateChar < '23:00:00' then 'PM'
ELSE 'NOC'
END -- CASE
END
Another approach would be to create a table with a row in it for every minute of the day, 1440 rows.
CREATE TABLE ShiftCheck
(
id int,
MyTime datetime not null,
ShiftNumber int not null,
ShiftName char(3) not null
CONSTRAINT [PK_ShiftCheck] PRIMARY KEY CLUSTERED
(
[id] ASC
)
)
populate that table with a statement similar to this
; WITH DateIntervalsCTE AS
(
SELECT 0 i, cast('1/1/1900' as datetime) AS Date
UNION ALL
SELECT i + 1, DATEADD(MINUTE, i,cast('1/1/1900' as datetime) )
FROM DateIntervalsCTE
WHERE DATEADD(MINUTE, i, cast('1/1/1900' as datetime) ) < cast('1/2/1900' as datetime)
)
SELECT
ROW_NUMBER() over (order by Date),
Date,
CASE
WHEN convert(varchar, Date, 108) >= '06:45:00' and convert(varchar, Date, 108) < '14:45:00' then 1
WHEN convert(varchar, Date, 108) >= '14:45:00' and convert(varchar, Date, 108) < '23:00:00' then 2
ELSE 3
END,
CASE
WHEN convert(varchar, Date, 108) >= '06:45:00' and convert(varchar, Date, 108) < '14:45:00' then 'AM'
WHEN convert(varchar, Date, 108) >= '14:45:00' and convert(varchar, Date, 108) < '23:00:00' then 'PM'
ELSE 'NOC'
END
FROM DateIntervalsCTE
OPTION (MAXRECURSION 32767);
Then, you just need to join to the ShiftCheck table on a time. There is only a need to calculate what time a shift is in one time, to populate the table.
Scalar Valued Functions, like the one listed in your question are executed for every row in a given query. For example
Select *,[dbo].[DateToNocShift](ShiftDate)
from myTable
the function will be executed for every row in myTable which at a certain point (Saturday morning while you are sleeping) will get very slow. In conclusion, this will eventually become a performance problem and eventually someone will want to see the word 1st, 2nd, 3rd for the shift name. This solution will solve both of those as well as force a look-up instead of a calculation.
*If that is not accurate enough, put a row for every second of the day (24*60*60 = 86400 rows, still not that big for sql server)
*I took the generate sql from here

Difference of two date time in sql server

Is there any way to take the difference between two datetime in sql server?
For example, my dates are
2010-01-22 15:29:55.090
2010-01-22 15:30:09.153
So, the result should be 14.063 seconds.
Just a caveat to add about DateDiff, it counts the number of times you pass the boundary you specify as your units, so is subject to problems if you are looking for a precise timespan.
e.g.
select datediff (m, '20100131', '20100201')
gives an answer of 1, because it crossed the boundary from January to February, so even though the span is 2 days, datediff would return a value of 1 - it crossed 1 date boundary.
select datediff(mi, '2010-01-22 15:29:55.090' , '2010-01-22 15:30:09.153')
Gives a value of 1, again, it passed the minute boundary once, so even though it is approx 14 seconds, it would be returned as a single minute when using Minutes as the units.
SELECT DATEDIFF (MyUnits, '2010-01-22 15:29:55.090', '2010-01-22 15:30:09.153')
Substitute "MyUnits" based on DATEDIFF on MSDN
SELECT DATEDIFF(day, '2010-01-22 15:29:55.090', '2010-01-22 15:30:09.153')
Replace day with other units you want to get the difference in, like second, minute etc.
I can mention four important functions of MS SQL Server that can be very useful:
1) The function DATEDIFF() is responsible to calculate differences between two dates, the result could be "year quarter month dayofyear day week hour minute second millisecond microsecond nanosecond", specified on the first parameter (datepart):
select datediff(day,'1997-10-07','2011-09-11')
2) You can use the function GETDATE() to get the actual time and calculate differences of some date and actual date:
select datediff(day,'1997-10-07', getdate() )
3) Another important function is DATEADD(), used to convert some value in datetime using the same datepart of the datediff, that you can add (with positive values) or substract (with negative values) to one base date:
select DATEADD(day, 45, getdate()) -- actual datetime adding 45 days
select DATEADD( s,-638, getdate()) -- actual datetime subtracting 10 minutes and 38 seconds
4) The function CONVERT() was made to format the date like you need, it is not parametric function, but you can use part of the result to format the result like you need:
select convert( char(8), getdate() , 8) -- part hh:mm:ss of actual datetime
select convert( varchar, getdate() , 112) -- yyyymmdd
select convert( char(10), getdate() , 20) -- yyyy-mm-dd limited by 10 characters
DATETIME cold be calculated in seconds and one interesting result mixing these four function is to show a formated difference um hours, minutes and seconds (hh:mm:ss) between two dates:
declare #date1 datetime, #date2 datetime
set #date1=DATEADD(s,-638,getdate())
set #date2=GETDATE()
select convert(char(8),dateadd(s,datediff(s,#date1,#date2),'1900-1-1'),8)
... the result is 00:10:38 (638s = 600s + 38s = 10 minutes and 38 seconds)
Another example:
select distinct convert(char(8),dateadd(s,datediff(s, CRDATE , GETDATE() ),'1900-1-1'),8) from sysobjects order by 1
I tried this way and it worked. I used SQL Server version 2016
SELECT DATEDIFF(MILLISECOND,'2010-01-22 15:29:55.090', '2010-01-22 15:30:09.153')/1000.00;
Different DATEDIFF Functions are:
SELECT DATEDIFF(year, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000');
SELECT DATEDIFF(quarter, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000');
SELECT DATEDIFF(month, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000');
SELECT DATEDIFF(dayofyear, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000');
SELECT DATEDIFF(day, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000');
SELECT DATEDIFF(week, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000');
SELECT DATEDIFF(hour, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000');
SELECT DATEDIFF(minute, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000');
SELECT DATEDIFF(second, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000');
SELECT DATEDIFF(millisecond, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000');
Ref: https://learn.microsoft.com/en-us/sql/t-sql/functions/datediff-transact-sql?view=sql-server-2017
Ok we all know the answer involves DATEDIFF(). But that gives you only half the result you may be after. What if you want to get the results in human-readable format, in terms of Minutes and Seconds between two DATETIME values?
The CONVERT(), DATEADD() and of course DATEDIFF() functions are perfect for a more easily readable result that your clients can use, instead of a number.
i.e.
CONVERT(varchar(5), DATEADD(minute, DATEDIFF(MINUTE, date1, date2), 0), 114)
This will give you something like:
HH:MM
If you want more precision, just increase the VARCHAR().
CONVERT(varchar(12), DATEADD(minute, DATEDIFF(MINUTE, date1, date2), 0), 114)
HH:MM.SS.MS
There are a number of ways to look at a date difference, and more when comparing date/times. Here's what I use to get the difference between two dates formatted as "HH:MM:SS":
ElapsedTime AS
RIGHT('0' + CAST(DATEDIFF(S, StartDate, EndDate) / 3600 AS VARCHAR(2)), 2) + ':'
+ RIGHT('0' + CAST(DATEDIFF(S, StartDate, EndDate) % 3600 / 60 AS VARCHAR(2)), 2) + ':'
+ RIGHT('0' + CAST(DATEDIFF(S, StartDate, EndDate) % 60 AS VARCHAR(2)), 2)
I used this for a calculated column, but you could trivially rewrite it as a UDF or query calculation. Note that this logic rounds down fractional seconds; 00:00.00 to 00:00.999 is considered zero seconds, and displayed as "00:00:00".
If you anticipate that periods may be more than a few days long, this code switches to D:HH:MM:SS format when needed:
ElapsedTime AS
CASE WHEN DATEDIFF(S, StartDate, EndDate) >= 359999
THEN
CAST(DATEDIFF(S, StartDate, EndDate) / 86400 AS VARCHAR(7)) + ':'
+ RIGHT('0' + CAST(DATEDIFF(S, StartDate, EndDate) % 86400 / 3600 AS VARCHAR(2)), 2) + ':'
+ RIGHT('0' + CAST(DATEDIFF(S, StartDate, EndDate) % 3600 / 60 AS VARCHAR(2)), 2) + ':'
+ RIGHT('0' + CAST(DATEDIFF(S, StartDate, EndDate) % 60 AS VARCHAR(2)), 2)
ELSE
RIGHT('0' + CAST(DATEDIFF(S, StartDate, EndDate) / 3600 AS VARCHAR(2)), 2) + ':'
+ RIGHT('0' + CAST(DATEDIFF(S, StartDate, EndDate) % 3600 / 60 AS VARCHAR(2)), 2) + ':'
+ RIGHT('0' + CAST(DATEDIFF(S, StartDate, EndDate) % 60 AS VARCHAR(2)), 2)
END
The following query should give the exact stuff you are looking out for.
select datediff(second, '2010-01-22 15:29:55.090' , '2010-01-22 15:30:09.153')
Here is the link from MSDN for what all you can do with datediff function .
https://msdn.microsoft.com/en-us/library/ms189794.aspx
Internally in SQL Server dates are stored as 2 integers. The first integer is the number of days before or after the base date (1900/01/01). The second integer stores the number of clock ticks after midnight, each tick is 1/300 of a second.
More info here
Because of this, I often find the simplest way to compare dates is to simply substract them. This handles 90% of my use cases. E.g.,
select date1, date2, date2 - date1 as DifferenceInDays
from MyTable
...
When I need an answer in units other than days, I will use DateDiff.
SELECT DATEDIFF(yyyy, '2011/08/25', '2017/08/25') AS DateDiff
It's gives you difference between two dates in Year
Here (2017-2011)=6 as a result
Syntax:
DATEDIFF(interval, date1, date2)
Use This for DD:MM:SS:
SELECT CONVERT(VARCHAR(max), Datediff(dd, '2019-08-14 03:16:51.360',
'2019-08-15 05:45:37.610'))
+ ':'
+ CONVERT(CHAR(8), Dateadd(s, Datediff(s, '2019-08-14 03:16:51.360',
'2019-08-15 05:45:37.610'), '1900-1-1'), 8)
So this isn't my answer but I just found this while searching around online for a question like this as well. This guy set up a procedure to calculate hours, minutes and seconds. The link and the code:
--Creating Function
If OBJECT_ID('UFN_HourMinuteSecond') Is Not Null
Drop Function dbo.UFN_HourMinuteSecond
Go
Exec(
'Create Function dbo.UFN_HourMinuteSecond
(
#StartDateTime DateTime,
#EndDateTime DateTime
) Returns Varchar(10)
As
Begin
Declare #Seconds Int,
#Minute Int,
#Hour Int,
#Elapsed Varchar(10)
Select #Seconds = ABS(DateDiff(SECOND ,#StartDateTime,#EndDateTime))
If #Seconds >= 60
Begin
select #Minute = #Seconds/60
select #Seconds = #Seconds%60
If #Minute >= 60
begin
select #hour = #Minute/60
select #Minute = #Minute%60
end
Else
Goto Final
End
Final:
Select #Hour = Isnull(#Hour,0), #Minute = IsNull(#Minute,0), #Seconds = IsNull(#Seconds,0)
select #Elapsed = Cast(#Hour as Varchar) + '':'' + Cast(#Minute as Varchar) + '':'' + Cast(#Seconds as Varchar)
Return (#Elapsed)
End'
)
declare #dt1 datetime='2012/06/13 08:11:12', #dt2 datetime='2012/06/12 02:11:12'
select CAST((#dt2-#dt1) as time(0))
PRINT DATEDIFF(second,'2010-01-22 15:29:55.090','2010-01-22 15:30:09.153')
select
datediff(millisecond,'2010-01-22 15:29:55.090','2010-01-22 15:30:09.153') / 1000.0 as Secs
result:
Secs
14.063
Just thought I'd mention it.
Sol-1:
select
StartTime
, EndTime
, CONVERT(NVARCHAR,(EndTime-StartTime), 108) as TimeDiff
from
[YourTable]
Sol-2:
select
StartTime
, EndTime
, DATEDIFF(hh, StartTime, EndTime)
, DATEDIFF(mi, StartTime, EndTime) % 60
from
[YourTable]
Sol-3:
select
DATEPART(hour,[EndTime]-[StartTime])
, DATEPART(minute,[EndTime]-[StartTime])
from
[YourTable]
Datepart works the best
Please check below trick to find the date difference between two dates
DATEDIFF(DAY,ordr.DocDate,RDR1.U_ProgDate) datedifff
where you can change according your requirement as you want difference of days or month or year or time.
CREATE FUNCTION getDateDiffHours(#fdate AS datetime,#tdate as datetime)
RETURNS varchar (50)
AS
BEGIN
DECLARE #cnt int
DECLARE #cntDate datetime
DECLARE #dayDiff int
DECLARE #dayDiffWk int
DECLARE #hrsDiff decimal(18)
DECLARE #markerFDate datetime
DECLARE #markerTDate datetime
DECLARE #fTime int
DECLARE #tTime int
DECLARE #nfTime varchar(8)
DECLARE #ntTime varchar(8)
DECLARE #nfdate datetime
DECLARE #ntdate datetime
-------------------------------------
--DECLARE #fdate datetime
--DECLARE #tdate datetime
--SET #fdate = '2005-04-18 00:00:00.000'
--SET #tdate = '2005-08-26 15:06:07.030'
-------------------------------------
DECLARE #tempdate datetime
--setting weekends
SET #fdate = dbo.getVDate(#fdate)
SET #tdate = dbo.getVDate(#tdate)
--RETURN #fdate
SET #fTime = datepart(hh,#fdate)
SET #tTime = datepart(hh,#tdate)
--RETURN #fTime
if datediff(hour,#fdate, #tdate) <= 9
RETURN(convert(varchar(50),0) + ' Days ' + convert(varchar(50),datediff(hour,#fdate, #tdate))) + ' Hours'
else
--setting working hours
SET #nfTime = dbo.getV00(convert(varchar(2),datepart(hh,#fdate))) + ':' +dbo.getV00(convert(varchar(2),datepart(mi,#fdate))) + ':'+ dbo.getV00(convert(varchar(2),datepart(ss,#fdate)))
SET #ntTime = dbo.getV00(convert(varchar(2),datepart(hh,#tdate))) + ':' +dbo.getV00(convert(varchar(2),datepart(mi,#tdate))) + ':'+ dbo.getV00(convert(varchar(2),datepart(ss,#tdate)))
IF #fTime > 17
begin
set #nfTime = '17:00:00'
end
else
begin
IF #fTime < 8
set #nfTime = '08:00:00'
end
IF #tTime > 17
begin
set #ntTime = '17:00:00'
end
else
begin
IF #tTime < 8
set #ntTime = '08:00:00'
end
-- used for working out whole days
SET #nfdate = dateadd(day,1,#fdate)
SET #ntdate = #tdate
SET #nfdate = convert(varchar,datepart(yyyy,#nfdate)) + '-' + convert(varchar,datepart(mm,#nfdate)) + '-' + convert(varchar,datepart(dd,#nfdate))
SET #ntdate = convert(varchar,datepart(yyyy,#ntdate)) + '-' + convert(varchar,datepart(mm,#ntdate)) + '-' + convert(varchar,datepart(dd,#ntdate))
SET #cnt = 0
SET #dayDiff = 0
SET #cntDate = #nfdate
SET #dayDiffWk = convert(decimal(18,2),#ntdate-#nfdate)
--select #nfdate,#ntdate
WHILE #cnt < #dayDiffWk
BEGIN
IF (NOT DATENAME(dw, #cntDate) = 'Saturday') AND (NOT DATENAME(dw, #cntDate) = 'Sunday')
BEGIN
SET #dayDiff = #dayDiff + 1
END
SET #cntDate = dateadd(day,1,#cntDate)
SET #cnt = #cnt + 1
END
--SET #dayDiff = convert(decimal(18,2),#ntdate-#nfdate) --datediff(day,#nfdate,#ntdate)
--SELECT #dayDiff
set #fdate = convert(varchar,datepart(yyyy,#fdate)) + '-' + convert(varchar,datepart(mm,#fdate)) + '-' + convert(varchar,datepart(dd,#fdate)) + ' ' + #nfTime
set #tdate = convert(varchar,datepart(yyyy,#tdate)) + '-' + convert(varchar,datepart(mm,#tdate)) + '-' + convert(varchar,datepart(dd,#tdate)) + ' ' + #ntTime
set #markerFDate = convert(varchar,datepart(yyyy,#fdate)) + '-' + convert(varchar,datepart(mm,#fdate)) + '-' + convert(varchar,datepart(dd,#fdate)) + ' ' + '17:00:00'
set #markerTDate = convert(varchar,datepart(yyyy,#tdate)) + '-' + convert(varchar,datepart(mm,#tdate)) + '-' + convert(varchar,datepart(dd,#tdate)) + ' ' + '08:00:00'
--select #fdate,#tdate
--select #markerFDate,#markerTDate
set #hrsDiff = convert(decimal(18,2),datediff(hh,#fdate,#markerFDate))
--select #hrsDiff
set #hrsDiff = #hrsDiff + convert(int,datediff(hh,#markerTDate,#tdate))
--select #fdate,#tdate
IF convert(varchar,datepart(yyyy,#fdate)) + '-' + convert(varchar,datepart(mm,#fdate)) + '-' + convert(varchar,datepart(dd,#fdate)) = convert(varchar,datepart(yyyy,#tdate)) + '-' + convert(varchar,datepart(mm,#tdate)) + '-' + convert(varchar,datepart(dd,#tdate))
BEGIN
--SET #hrsDiff = #hrsDiff - 9
Set #hrsdiff = datediff(hour,#fdate,#tdate)
END
--select FLOOR((#hrsDiff / 9))
IF (#hrsDiff / 9) > 0
BEGIN
SET #dayDiff = #dayDiff + FLOOR(#hrsDiff / 9)
SET #hrsDiff = #hrsDiff - FLOOR(#hrsDiff / 9)*9
END
--select convert(varchar(50),#dayDiff) + ' Days ' + convert(varchar(50),#hrsDiff) + ' Hours'
RETURN(convert(varchar(50),#dayDiff) + ' Days ' + convert(varchar(50),#hrsDiff)) + ' Hours'
END
For Me This worked Perfectly
Convert(varchar(8),DATEADD(SECOND,DATEDIFF(SECOND,LogInTime,LogOutTime),0),114)
and the Output is
HH:MM:SS which is shown accurately in my case.
Please try
DECLARE #articleDT DATETIME;
DECLARE #nowDate DATETIME;
-- Time of the ARTICLE created
SET #articleDT = '2012-04-01 08:10:16';
-- Simulation of NOW datetime
-- (in real world you would probably use GETDATE())
SET #nowDate = '2012-04-10 11:35:36';
-- Created 9 days ago.
SELECT 'Created ' + CAST(DATEDIFF(day, #articleDT, #nowDate) AS NVARCHAR(50)) + ' days ago.';
-- Created 1 weeks, 2 days, 3 hours, 25 minutes and 20 seconds ago.
SELECT 'Created '
+ CAST(DATEDIFF(second, #articleDT, #nowDate) / 60 / 60 / 24 / 7 AS NVARCHAR(50)) + ' weeks, '
+ CAST(DATEDIFF(second, #articleDT, #nowDate) / 60 / 60 / 24 % 7 AS NVARCHAR(50)) + ' days, '
+ CAST(DATEDIFF(second, #articleDT, #nowDate) / 60 / 60 % 24 AS NVARCHAR(50)) + ' hours, '
+ CAST(DATEDIFF(second, #articleDT, #nowDate) / 60 % 60 AS NVARCHAR(50)) + ' minutes and '
+ CAST(DATEDIFF(second, #articleDT, #nowDate) % 60 AS NVARCHAR(50)) + ' seconds ago.';
For MS SQL, you can convert the datetime value to a double value.
The integer part contains the number of days since 1900-01-01, the fractional part contains the time in hours.
So you can calculate date difference as:
cast(date1-date2 as FLOAT)