27

I am trying to group some records into 5-, 15-, 30- and 60-minute intervals:

SELECT AVG(value) as "AvgValue",
sample_date/(5*60) as "TimeFive"
FROM DATA
WHERE id = 123 AND sample_date >= 3/21/2012

i want to run several queries, each would group my average values into the desired time increments. So the 5-min query would return results like this:

AvgValue  TimeFive
6.90      1995-01-01 00:05:00
7.15      1995-01-01 00:10:00
8.25      1995-01-01 00:15:00

The 30-min query would result in this:

AvgValue  TimeThirty 
6.95      1995-01-01 00:30:00
7.40      1995-01-01 01:00:00

The datetime column is in yyyy-mm-dd hh:mm:ss format

I am getting implicit conversion errors of my datetime column. Any help is much appreciated!

JotaBe
  • 38,030
  • 8
  • 98
  • 117
jrubengb
  • 363
  • 1
  • 7
  • 14
  • It's not obvious to me what you mean by 'grouping into different intervals'. Recalling that results are necessarily rows in columns, can you show what sample rows of your data will look like? In particular it's not clear to me, will one id be in 1 5 minute group, also in one 15 minute group, also in one 30 minute group...etc. If each record will be in 4 groups, for example, what are the names of the columns you want in the result? – Levin Magruder Mar 22 '12 at 00:45
  • 2
    Also, if you're going to be posting a number of sql questions, 1) always tag the question with the specific type of server (I see from the title it's MS SQL, but if you used tags they'd suggest to you that you could specify a version) and 2) you can lower the "cost of figuring out what the question means" by taking the time to set up a sql fiddle (http://sqlfiddle.org); it lets you put in examples data you want to query. – Levin Magruder Mar 22 '12 at 00:49
  • 1
    Just to clear up something: `DATETIME` in SQL Server is **never** stored in a string-based format - it's stored as two 4-byte INT values internally. That format might be your **default presentation** - but it's **NOT** stored in that format! – marc_s Mar 22 '12 at 06:12
  • Is `sample_date` actually of type `datetime`? If so, it doesn't *have* a format (and this is good) – Damien_The_Unbeliever Mar 22 '12 at 06:16
  • the sample_date is of type (smalldatetime, not null) – jrubengb Mar 22 '12 at 15:52
  • I've gone ahead and added the sql-server-2008 tag to the post. thanks for the suggestion. – jrubengb Mar 22 '12 at 15:53
  • I have revised the post to hopefully better illustrate what I mean by 'grouping into different intervals' and added example results. Basically I am wanting to run separate queries, rather than return results in the same table. i guess if that were possible that might be better, but I didn't consider that. – jrubengb Mar 22 '12 at 15:55

6 Answers6

29

Using

datediff(minute, '1990-01-01T00:00:00', yourDatetime)

will give you the number of minutes since 1990-1-1 (you can use the desired base date).

Then you can divide by 5, 15, 30 or 60, and group by the result of this division. I've cheked it will be evaluated as an integer division, so you'll get an integer number you can use to group by.

i.e.

group by datediff(minute, '1990-01-01T00:00:00', yourDatetime) /5

UPDATE As the original question was edited to require the data to be shown in date-time format after the grouping, I've added this simple query that will do what the OP wants:

-- This convert the period to date-time format
SELECT 
    -- note the 5, the "minute", and the starting point to convert the 
    -- period back to original time
    DATEADD(minute, AP.FiveMinutesPeriod * 5, '2010-01-01T00:00:00') AS Period,
    AP.AvgValue
FROM
    -- this groups by the period and gets the average
    (SELECT
        P.FiveMinutesPeriod,
        AVG(P.Value) AS AvgValue
    FROM
        -- This calculates the period (five minutes in this instance)
        (SELECT 
            -- note the division by 5 and the "minute" to build the 5 minute periods
            -- the '2010-01-01T00:00:00' is the starting point for the periods
            datediff(minute, '2010-01-01T00:00:00', T.Time)/5 AS FiveMinutesPeriod,
            T.Value
        FROM Test T) AS P
    GROUP BY P.FiveMinutesPeriod) AP

NOTE: I've divided this in 3 subqueries for clarity. You should read it from inside out. It could, of course, be written as a single, compact query

NOTE: if you change the period and the starting date-time you can get any interval you need, like weeks starting from a given day, or whatever you can need

If you want to generate test data for this query use this:

CREATE TABLE Test
( Id INT IDENTITY PRIMARY KEY,
Time DATETIME,
Value FLOAT)

INSERT INTO Test(Time, Value) VALUES('2012-03-22T00:00:22', 10)
INSERT INTO Test(Time, Value) VALUES('2012-03-22T00:03:22', 10)
INSERT INTO Test(Time, Value) VALUES('2012-03-22T00:04:45', 10)
INSERT INTO Test(Time, Value) VALUES('2012-03-22T00:07:21', 20)
INSERT INTO Test(Time, Value) VALUES('2012-03-22T00:10:25', 30)
INSERT INTO Test(Time, Value) VALUES('2012-03-22T00:11:22', 30)
INSERT INTO Test(Time, Value) VALUES('2012-03-22T00:14:47', 30)

The result of executing the query is this:

Period                     AvgValue
2012-03-22 00:00:00.000    10
2012-03-22 00:05:00.000    20
2012-03-22 00:10:00.000    30
JotaBe
  • 38,030
  • 8
  • 98
  • 117
  • 1
    I'd suggest `19900101` rather than `1990-1-1`, since the dashed format for specifying dates without times is ambiguous. (Of course, it's okay in this specific example, but if month and day are different, SQL Server may interpret it in one of two ways, or raise a conversion error) – Damien_The_Unbeliever Mar 22 '12 at 06:15
  • Thanks for the advice. I ran this query and it seemed to group my average values into rows in the results, but I'm not able to get the aggregate time value to appear in the query results. I have edited your post to describe my attempt to get the time column to appear in the query results – jrubengb Mar 22 '12 at 15:58
  • I tried using the '19900101' format as Damien_The_Unbeliever described, and that does appear to work, however I did not see any differences in the initial query results. Thanks for the suggestion. – jrubengb Mar 22 '12 at 16:05
  • @jrubengb: use `DATEADD` and simple maths to get the datetime back from the number. I've updated my answer. – JotaBe Sep 30 '14 at 08:42
  • Regarding the previous comments about the date format, the `YYYY-MM-DDThh:mm:ss`, is the ISO format which is universally understood in every SQL Server (and any other software which knows this format) and independent of the language or any other related settings. – JotaBe Jul 10 '19 at 13:57
19

Building on @JotaBe's answer (to which I cannot comment on--otherwise I would), you could also try something like this which would not require a subquery.

 SELECT 
    AVG(value) AS 'AvgValue',

    -- Add the rounded seconds back onto epoch to get rounded time
    DATEADD(
        MINUTE,
        (DATEDIFF(MINUTE, '1990-01-01T00:00:00', your_date) / 30) * 30,
        '1990-01-01T00:00:00'
    )      AS 'TimeThirty'

 FROM YourTable
 -- WHERE your_date > some max lookback period
 GROUP BY
    (DATEDIFF(MINUTE, '1990-01-01T00:00:00', your_date) / 30)

This change removes temp tables and subqueries. It uses the same core logic for grouping by 30 minute intervals but, when presenting the data back as part of the result I'm just reversing the interval calculation to get the rounded date & time.

DJ Sipe
  • 1,286
  • 13
  • 12
  • Citing myself: NOTE: I've divided this in 3 subqueries for clarity. You should read it from inside out. It could, of course, be written as a single, compact query – JotaBe Apr 12 '22 at 11:55
9

So, in case you googled this, but you need to do it in mysql, which was my case:

In MySQL you can do

GROUP BY
CONCAT(
    DATE_FORMAT(`timestamp`,'%m-%d-%Y %H:'),
    FLOOR(DATE_FORMAT(`timestamp`,'%i')/5)*5
)
chiliNUT
  • 18,989
  • 14
  • 66
  • 106
4

In the new SQL Server 2022, you can use DATE_BUCKET, this rounds it down to the nearest interval specified.

SELECT
  DATE_BUCKET(minute, 5, d.sample_date) AS TimeFive,
  AVG(d.value) AS AvgValue  
FROM DATA d
WHERE d.id = 123
  AND d.sample_date >= '20121203'
GROUP BY
  DATE_BUCKET(minute, 5, d.sample_date);
Charlieface
  • 52,284
  • 6
  • 19
  • 43
0

You can use the following statement, this removed the second component and calculates the number of minutes away from the five minute mark and uses this to round down to the time block. This is ideal if you want to change your window, you can simply change the mod value.

select dateadd(minute, - datepart(minute,  [YOURDATE]) % 5, dateadd(minute, datediff(minute, 0, [YOURDATE]), 0)) as [TimeBlock]
Tim
  • 298
  • 1
  • 2
  • 11
-1

This will help exactly what you want

replace dt - your datetime c - call field astro_transit1 - your table 300 refer 5 min so add 300 each time for time gap increase

SELECT FROM_UNIXTIME( 300 * ROUND( UNIX_TIMESTAMP( r.dt ) /300 ) ) AS 5datetime, ( SELECT r.c FROM astro_transit1 ra WHERE ra.dt = r.dt ORDER BY ra.dt DESC LIMIT 1 ) AS first_val FROM astro_transit1 r GROUP BY UNIX_TIMESTAMP( r.dt ) DIV 300 LIMIT 0 , 30
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103