Skip to content
Advertisement

Database Query to generate a Time-based Chart

I have a logins table in the following (simplified) structure:

id | login_time
---------
1  | 2019-02-04 18:14:30.026361+00
2  | 2019-02-04 22:10:19.720065+00
3  | 2019-02-06 15:51:53.799014+00

Now I want to generate chart like this:

https://prnt.sc/mifz6y

Basically I want to show the logins within the past 48 hours.

My current query:

SELECT count(*), date_trunc('hour', login_time) as time_trunced FROM user_logins
WHERE login_time > now() - interval '48' hour
GROUP BY time_trunced
ORDER BY time_trunced DESC

This works as long as there are entries for every hour. However, if in some hour there were no logins, there will be no entry selected, like this:

time_trunced | count
---------------------
12:00        | 1
13:00        | 2
15:00        | 3
16:00        | 5

I would need a continous query, so that I can simply put the count values into an array:

time_trunced | count
---------------------
12:00        | 1
13:00        | 2
14:00        | 0 <-- This is missing
15:00        | 3
16:00        | 5

Based on that I can simply transform the query result into an array like [1, 2, 0, 3, 5] and pass that to my frontend.

Is this possible with postgresql? Or do I need to implement my own logic?

Advertisement

Answer

This can almost certainly be tidied up a bit but should give you some ides. Props to clamp for the generate_series() tip :

SELECT t.time_trunced,coalesce(l.login_count,0) as logins 
FROM
(
 -- Generate an inline view with all hours between the min & max values in user_logins table
 SELECT date_trunc('hour',a.min_time)+ interval '1h' * b.hr_offset as time_trunced
 FROM (select min(login_time) as min_time from user_logins) a
 JOIN (select generate_series(0,(select ceil((EXTRACT(EPOCH FROM max(login_time))-EXTRACT(EPOCH FROM min(login_time)))/3600) from user_logins)::int) as hr_offset) b on true
) t
LEFT JOIN 
 (
 -- OP's original query tweaked a bit
 SELECT count(*) as login_count, date_trunc('hour', login_time) as time_trunced
 FROM user_logins
 GROUP BY time_trunced
) l on t.time_trunced=l.time_trunced
order BY 1 desc;
User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement