TS SQL - group by minute - sql-server

I have a table with timestamps. What is the proper query to get the records counts for each minute for the last hour.
I.e. if now is 2:25, I want to know how many record were between 1:25 and 1:26, 1:26 and 1:27, and so on, so I have 60 results.

This will return a count of results for each minute (where you have records) in the last hour
SELECT DATEPART(n, time_stamp) AS minute, COUNT(*) as results
FROM table_name
WHERE time_stamp > DATEADD(hh, -1, GETDATE())
GROUP BY DATEPART(n, time_stamp)
This may return less than 60 results, depending on the data. If you have to have 60 results, the query is slightly different. This uses a Common Table Expression to generate a list of 60 numbers and a correlated sub-query to get the results for each minute:
WITH numbers ( num ) AS (
SELECT 1 UNION ALL
SELECT 1 + num FROM numbers WHERE num < 60 )
SELECT num AS minute,
(SELECT COUNT(*) AS results
FROM table_name
WHERE DATEPART(n, time_stamp) = num
AND time_stamp > DATEADD(hh, -1, GETDATE())
FROM numbers
To see the results, replace DATEADD(hh, -1, GETDATE()) with DATEADD(mi, -15, GETDATE()) and you'll get the results for the last 15 minutes and 0 for other minutes.

This is an alternative I have found useful for determining how many records are inserted or updated per minute. The nice thing about having your date format as a variable up front is that you can easily change it to analyze per hour instead. Hope this helps!
DECLARE #dateFormat as varchar(max) = 'yyyy-MM-dd HH:mm'
SELECT format(timeColumn, #dateFormat) AS minute, COUNT(*) as results
FROM yourTable
WHERE timeColumn > DATEADD(hh, -1, GETDATE())
GROUP BY format(timeColumn, #dateFormat)
ORDER BY 1

As you edited the question, I edit my answer. If I have understood you correctly, you want to look only at the past hour - that is, a timespan from one hour before the request is made to the current time. This is how I'd do it:
SELECT
COUNT(yourTimeStamp)
FROM yourTable
WHERE DATEADD('hh', -1, GetDate()) <= yourTimeStamp
AND yourTimeStamp < GetDate()
GROUP BY DATEPART('mm', yourTimeStamp)
I am not entirely sure that the syntax is exact. When coding in MSSQL, I would use the CURRENT_TIMESTAMP for the current time, MINUTE instead of DATEPART etc, but you get the idea for the solution.

DATEPART is what you're looking for:
declare #times table
(
someTime datetime
)
INSERT INTO #Times (sometime) values ('jan 12 2008 12:23')
INSERT INTO #Times (sometime) values ('jan 12 2008 12:34')
INSERT INTO #Times (sometime) values ('jan 12 2008 12:35')
INSERT INTO #Times (sometime) values ('jan 12 2008 12:25')
INSERT INTO #Times (sometime) values ('jan 12 2008 12:02')
INSERT INTO #Times (sometime) values ('jan 12 2008 12:09')
INSERT INTO #Times (sometime) values ('jan 12 2008 12:35')
select DATEPART(mi,sometime) AS Minute, count(*) AS NumOccurances
from #Times
WHERE SomeTime BETWEEN #Lower AND #Upper
GROUP BY DATEPART(mi, sometime)
order by NumOccurances DESC
Result:
Minute NumOccurances
35 2
2 1
9 1
23 1
25 1
34 1

If you want to group results by minute, then you can use a formatted string. This will group by number of minutes since 1/1/1900 not minute within day.
WITH formatted AS (
SELECT FORMAT(<your_datetime_column>, 'yyyy-MM-dd HH:mm') AS minute_text
FROM <your_table>
)
SELECT minute_text, COUNT(*) AS number_of_rows
FROM formatted
GROUP BY minute_text
ORDER BY 1 DESC

Here's my fixed up version of Robin's answer. I made it output the errors in the correct order and output the time as well instead of just the number which isn't super useful if you're charting this out.
WITH numbers ( num ) AS (
SELECT 1 UNION ALL
SELECT 1 + num FROM numbers WHERE num < 60 )
SELECT (SELECT DATEADD(n, -num, GETDATE())) AS TimeStamp,
(SELECT COUNT(*) AS results
FROM ErrorLogs
WHERE DATEPART(n, TimeStamp) = DATEPART(n, DATEADD(n, -num, GETDATE()))
AND TimeStamp > DATEADD(hh, -1, GETDATE())) as Count
FROM numbers

SELECT COUNT (TS) from TABLE where TABLE.TS BETWEEN(starttime, endtime)

Related

Evaluating datetime into timewindows

Im trying to establish for any given datetime a tag that is purely dependent on the time part.
However because the time part is cyclic I cant make it work with simple greater lower than conditions.
I tried a lot of casting and shift one time to 24hour mark to kinda break the cycle However it just gets more and more complicated and still doesnt work.
Im using SQL-Server, here is the situation:
DECLARE #tagtable TABLE (tag varchar(10),[start] time,[end] time);
DECLARE #datetimestable TABLE ([timestamp] datetime)
Insert Into #tagtable (tag, [start], [end])
values ('tag1','04:00:00.0000000','11:59:59.9999999'),
('tag2','12:00:00.0000000','19:59:59.9999999'),
('tag3','20:00:00.0000000','03:59:59.9999999');
Insert Into #datetimestable ([timestamp])
values ('2022-07-24T23:05:23.120'),
('2022-07-27T13:24:40.650'),
('2022-07-26T09:00:00.000');
tagtable:
tag
start
end
tag1
04:00:00.0000000
11:59:59.9999999
tag2
12:00:00.0000000
19:59:59.9999999
tag3
20:00:00.0000000
03:59:59.9999999
for given datetimes e.g. 2022-07-24 23:05:23.120, 2022-07-27 13:24:40.650, 2022-07-26 09:00:00.000
the desired result would be:
date
tag
2022-07-25
tag3
2022-07-27
tag2
2022-07-26
tag1
As I wrote i tried to twist this with casts and adding and datediffs
SELECT
If(Datepart(Hour, a.[datetime]) > 19,
Cast(Dateadd(Day,1,a.[datetime]) as Date),
Cast(a.[datetime] as Date)
) as [date],
b.[tag]
FROM #datetimestable a
INNER JOIN #tagtable b
ON SomethingWith(a.[datetime])
between SomethingWith(b.[start]) and SomethingWith(b.[end])
The only tricky bit here is that your tag time ranges can go over midnight, so you need to check that your time is either between start and end, or if it spans midnight its between start and 23:59:59 or between 00:00:00 and end.
The only other piece is splitting your timestamp column into date and time using a CTE, to save having to repeat the cast.
;WITH splitTimes AS
(
SELECT CAST(timestamp AS DATE) as D,
CAST(timestamp AS TIME) AS T
FROM #datetimestable
)
SELECT
DATEADD(
day,
CASE WHEN b.[end]<b.start THEN 1 ELSE 0 END,
a.D) as timestamp,
b.[tag]
FROM [splitTimes] a
INNER JOIN #tagtable b
ON a.T between b.[start] and b.[end]
OR (b.[end]<b.start AND (a.T BETWEEN b.[start] AND '23:59:59.99999'
OR a.T BETWEEN '00:00:00' AND b.[end]))
Live example: https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=506aef05b5a761afaf1f67a6d729446c
Since they're all 8-hour shifts, we can essentially ignore the end (though, generally, trying to say an end time is some specific precision of milliseconds will lead to a bad time if you ever use a different data type (see the first section here) - so if the shift length will change, just put the beginning of the next shift and use >= start AND < end instead of BETWEEN).
;WITH d AS
(
SELECT datetime = [timestamp],
date = CONVERT(datetime, CONVERT(date, [timestamp]))
FROM dbo.datetimestable
)
SELECT date = DATEADD(DAY,
CASE WHEN t.start > t.[end] THEN 1 ELSE 0 END,
CONVERT(date, date)),
t.tag
FROM d
INNER JOIN dbo.tagtable AS t
ON d.datetime >= DATEADD(HOUR, DATEPART(HOUR, t.start), d.date)
AND d.datetime < DATEADD(HOUR, 8, DATEADD(HOUR,
DATEPART(HOUR, t.start), d.date));
Example db<>fiddle
Here's a completely different approach that defines the intervals in terms of starts and durations rather than starts and ends.
This allows the creation of tags that can span multiple days, which might seem like an odd capability to have here, but there might be a use for it if we add some more conditions down the line. For example, say we want to be able say "anything from 6pm friday to 9am monday gets the 'out of hours' tag". Then we could add a day of week predicate to the tag definition, and still use the duration-based interval.
I have defined the duration granularity in terms of hours, but of course this can easily be changed
create table #tags
(
tag varchar(10),
startTimeInclusive time,
durationHours int
);
insert #tags
values ('tag1','04:00:00', 8),
('tag2','12:00:00', 8),
('tag3','20:00:00', 8);
create table #dateTimes (dt datetime)
insert #dateTimes
values ('2022-07-24T23:05:23.120'),
('2022-07-27T13:24:40.650'),
('2022-07-26T09:00:00.000');
select dt.dt,
t.tag
from #datetimes dt
join #tags t on cast(dt.dt as time) >= t.startTimeInclusive
and dt.dt < dateadd
(
hour,
t.durationHours,
cast(cast(dt.dt as date) as datetime) -- strip the time from dt
+ cast(t.startTimeInclusive as datetime) -- add back the time from t
);
Maybe I am looking at this to simple, but,
can't you just take the first tag with an hour greater then your hour in table datetimestable.
With an order by desc it should always give you the correct tag.
This will work well as long as you have no gaps in your tagtable
select case when datepart(hour, tag.tagStart) > 19 then dateadd(day, 1, convert(date, dt.timestamp))
else convert(date, dt.timestamp)
end as [date],
tag.tag
from datetimestable dt
outer apply ( select top 1
tt.tag,
tt.tagStart
from tagtable tt
where datepart(Hour, dt.timestamp) > datepart(hour, tt.tagStart)
order by tt.tagStart desc
) tag
It returns the correct result in this DBFiddle
The result is
date
tag
2022-07-25
tag3
2022-07-27
tag2
2022-07-26
tag1
EDIT
If it is possible that there are gaps in the table,
then I think the most easy and solid solution would be to split that row that passes midnight into 2 rows, and then your query can be very simple
See this DBFiddle
select case when datepart(hour, tag.tagStart) > 19 then dateadd(day, 1, convert(date, dt.timestamp))
else convert(date, dt.timestamp)
end as [date],
tag.tag
from datetimestable dt
outer apply ( select tt.tag,
tt.tagStart
from tagtable tt
where datepart(Hour, dt.timestamp) >= datepart(hour, tt.tagStart)
and datepart(Hour, dt.timestamp) <= datepart(hour, tt.tagEnd)
) tag

SQL - Counting incidents at time period (done) but including another variable

I'm newish to SQL so sorry if the code is a little scruffy.
Basically I am creating a count of fire engines in use on every hour, which I have done, and that bit works. So I have a count of this for the past five years. Sorted.
But now I want to run it for a specific group of incidents (about 300 of them), showing how many engines were at that incident, every hour, and how many others were in use at the same time, but somewhere else.
My basic working code (that I modified from https://stackoverflow.com/a/43337534/5880512) is as follows. It just counts all P1 and P2 mobilisations at the defined time.
DECLARE #startdate datetime = '2018-05-03 00:00:00'
DECLARE #enddate datetime = '2018-05-05 00:00:00'
;with cte as
(
select #startdate startdate
union all
select DATEADD(minute, 60, startdate)
FROM cte
WHERE DATEADD(minute, 60, startdate) < #enddate
)
select convert(varchar(20), startdate, 120) as CreationTime, (select count(*) FROM MB_MOBILISATIONS WHERE MB_SEND < startdate and MB_LEAVE > startdate And (MB_CALL_SIGN Like '%P1' Or MB_CALL_SIGN Like '%P2')) as Count
from cte
option (maxrecursion 0)
To split these up for a particular incident, I can put the incident ref into the where clause, one as = so it will give me engines at that incident, and one as <> so it gives me the rest. This bit works too.
select convert(varchar(20), startdate, 120) as CreationTime, (select count(*) FROM MB_MOBILISATIONS WHERE MB_SEND < startdate and MB_LEAVE > startdate And (MB_CALL_SIGN Like '%P1' Or MB_CALL_SIGN Like '%P2') and MB_IN_REF = 1704009991) as 'At Incident'
, select convert(varchar(20), startdate, 120) as CreationTime, (select count(*) FROM MB_MOBILISATIONS WHERE MB_SEND < startdate and MB_LEAVE > startdate And (MB_CALL_SIGN Like '%P1' Or MB_CALL_SIGN Like '%P2') and MB_IN_REF <> 1704009991) as 'Other Incident'
The bit I can't work out to do, is to make this work for multiple incidents, without having to change the incident reference manually in the where clause for all 300.
The incident references I want to use will be stored in a temporary table. Ideally, I would like it to pick an ID, set the variables #startdate and #enddate, from the start and end of that incident, then do the hourly count for the duration of that incident.
Hopefully the results would look something like this
IncidentRef DateTime At Incident Other Incident
A 2018-05-03 1:00 4 2
A 2018-05-03 2:00 7 3
A 2018-05-03 3:00 5 3
A 2018-05-03 4:00 2 4
B 2017-03-01 9:00 7 2
B 2017-03-01 10:00 8 3
B 2017-03-01 11:00 6 1
B 2017-03-01 12:00 4 2
I hope that makes sense.
Thanks :)
Use something like this to limit the scope of your search to a smaller list. I've just added and referenced another CTE with a filter. If you're looking to parameterize the list you'll need a different approach like storing those id values in another table first.
with cte as (
select #startdate startdate
union all
select dateadd(minute, 60, startdate)
from cte
where dateadd(minute, 60, startdate) < #enddate
), mobi as (
select * from MB_MOBILISATIONS
where MB_IN_REF in (<insert list here>)
)
select convert(varchar(20), startdate, 120) as CreationTime, m."Count"
from cte cross apply (
select count(*) as "Count" from mobi
where MB_SEND < startdate and MB_LEAVE > startdate and
(MB_CALL_SIGN like '%P1' or MB_CALL_SIGN like '%P2')
) m;
I went ahead and rewrote your scalar subquery but I guess that's just a personal preference.

How to auto-add dates in column, SQL Server 2014

I'm very new to SQL Server and I want to have dates from today up to 30 days ahead of todays date in one column, which way is the most considered efficient and "correct" way? ( I'm not asking for code ).
I read that loops should preferably be avoided in SQL Server, is that correct? Also, I thought of solving the date-issue with using a logon trigger (adding 30 days ahead of today whenever a logon happens), anyone know a more efficient and "correct" way?
Thanks
You can use recursive CTE to get sequential dates for next 30 days.
CREATE TABLE Dates
(
allDates DATE
)
;WITH MyCTE
AS (SELECT getdate() AS ddate,
dateadd(day, 30, getdate()) AS lastDate
UNION ALL
SELECT dateadd(day, 1, ddate),
lastDate
FROM MyCTE
WHERE dateadd(day, 1, ddate) <= lastDate)
INSERT INTO Dates(allDates)
SELECT ddate FROM MyCTE
SELECT * FROM Dates
SQL Fiddle Demo
The most efficient way to do this would be a Job. SQL Server Agent provides the ability to run any script you want on any interval you choose. A very simplistic approach would be to create a job which runs nightly and inserts a row for [Today + 30 Days].
I believe you are seeking 30 rows from a query with each row representing a date starting at today, and finishing 30 days after today.
There are many potential solutions for this that don't use a cursor/loop, for example
select
dateadd(day,nums.number,nums.today) as a_date
from (
select
number
, cast(getdate() as date) as today
FROM master.dbo.spt_values as sv
WHERE sv.type = 'P'
AND sv.number BETWEEN 0 and 29
) nums
see: this SQLfiddle demo
Note that query is using master.dbo.spt_values and some prefer not to use this (refer here). So instead you could use a small union all with cross join to generate the rows, or you can use a recursive "common table expression" (CTE) as an alternative.
;WITH
Digits AS (
SELECT 0 AS digit 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 UNION ALL SELECT 8 UNION ALL SELECT 9
)
, Tally AS (
SELECT [tens].digit * 10 + [ones].digit AS number
FROM Digits [ones]
CROSS JOIN Digits [tens]
)
select
dateadd(day,nums.number,nums.today) as a_date
from (
select
number
, cast(getdate() as date) as today
FROM tally
WHERE number BETWEEN 0 and 29
) nums
To get todays date + 30 days do this:
select dateadd(dd,30,getdate())

Group data rows by near time

Here is the problem I am facing:
I got a large table containing rows, I want to group them by near time, more specifically the time difference less than 2 minutes, example as following
With following input data:
A 16:01:01
B 16:01:20
C 16:14:02
D 16:15:01
E 16:20:02
the expected result is
16:01:01 2
16:14:02 2
16:20:02 1
If you're using SQL server 2012, you'r in luck and you can use lag function and rolling total sum:
with cte as (
select
case
when datediff(mi, lag(data) over (order by data), data) <= 1 then 0
else 1
end as ch,
data
from test
), cte2 as (
select
data, sum(ch) over (order by data) as grp
from cte
)
select
min(data) as data, count(*) as cn
from cte2
group by grp
sql fiddle demo
SELECT CONVERT(VARCHAR(8),
DATEADD(minute, (DATEDIFF(n, 0, time) / 2) * 2, 0),
108),
COUNT(*)
FROM times
GROUP BY DATEDIFF(n, 0, time) / 2
Explanation:
CONVERT displays a DateTime in hh:mm:ss format (= 108).
DATEDIFF converts to minutes and then divides by two, rounding to an integer so each GROUP of 2 minutes resolves to the same integer.
DATEADD is used to convert this number of minutes back to a DateTime, having multiplied by 2 to get back to the correct (rounded) time.
See SQL Fiddle Demo
Declare #m_TestTable table
(
DateRecorded datetime
)
Insert into #m_TestTable Values ('16:01:01' )
Insert into #m_TestTable Values ('16:01:20' )
Insert into #m_TestTable Values ('16:14:02' )
Insert into #m_TestTable Values ('16:15:01' )
Insert into #m_TestTable Values ('16:20:01' );
With tblDifference as
(
Select Row_Number() OVER (Order by DateRecorded) as RowNumber,DateRecorded from #m_TestTable
)
select cur.DateRecorded as prvD, prv.DateRecorded as prvC, dateDiff(n, cur.DateRecorded,prv.DateRecorded) from tblDifference cur LEFT OUTER JOIN tblDifference prv
ON cur.RowNumber = prv.RowNumber + 1
this will give you the time difference in minutes between 2 rows. You can select any row that has a time difference less then 2 mins. It will also give you the upper and lower value.
It should be usefull to find any values closer then 2 minutes apart.
prvD prvC Diff
1900-01-01 16:01:01.000 NULL NULL
1900-01-01 16:01:20.000 1900-01-01 16:01:01.000 0
1900-01-01 16:14:02.000 1900-01-01 16:01:20.000 -13
1900-01-01 16:15:01.000 1900-01-01 16:14:02.000 -1
1900-01-01 16:20:01.000 1900-01-01 16:15:01.000 -5

SQL QUERY ( SQL SERVER) Date & Time Difference

I have a SQL SERVER Table which has only one field "StartDate" the records are as follows
**
2011-07-28 19:30:00.000
2011-07-29 21:50:00.000
2011-07-25 09:20:00.000
**
What i want to do is :
SHOW RECORDS if its CURRENT DATE ( todays date ) and the time difference between current time the StartDate is not less then 5 minutes, i have written the following code but it doesnt show me the time difference ?
SELECT * FROM table WHERE DATEDIFF(day, StartDate, GETDATE()) <= 0
SELECT StartDate
FROM table
WHERE YEAR(StartDate)=YEAR(GETDATE())
AND MONTH(StartDate)=MONTH(GETDATE())
AND DAY(StartDate)=DAY(GETDATE())
AND (DATEDIFF(minute, StartDate, GETDATE()) >= 5
OR
DATEDIFF(minute, StartDate, GETDATE()) <= 5)
How about:
SELECT StartDate
,GETDATE()
,DATEDIFF(day, StartDate, GETDATE())
,DATEDIFF(minute, StartDate, GETDATE())
,*
FROM table
WHERE DATEDIFF(day, StartDate, GETDATE()) <= 0
AND DATEDIFF(minute, StartDate, GETDATE()) >= 5
There are two ways to do it one being DateDiff the other is DATEADD. Judging by the way I read your question DateAdd should do it. Here is an example;
SELECT *
FROM [dbo].[TABLE]
WHERE [LAST_UPDATE] > DATEADD(minute,-5,GetDate())
Using BETWEEN is probably a little more optimal than two AND statements (maybe not). Try not to do a calculation on each row if you don't have to. Doing DATEADD only on the current date will only be calculated once.
SELECT
whatever
FROM
table
WHERE
StartDate
BETWEEN FLOOR( CAST( GETDATE() AS FLOAT ) )
AND DATEADD(minute, -5, GETDATE())
I interpret the question as looking for rows where the date is today (between the start of today) but not within the last 5 minutes (and less than 5 minutes ago). That might not be what you were going for.
Hope that helps

Resources