Get daily count of rows for a Time - sql

My database table looks like this:
CREATE TABLE record
(
id INT,
status INT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
And I want to create a generic query to get count of record created after 3 hours of interval in last day
For example, I want to know in last 1 day after 3 hours how many records are created.
What I have so far: with a little help from stackoverflow I am able to create a query to calculate the count for a single full day.
SELECT
DATE(created_at) AS day, COUNT(1)
FROM
record
WHERE
created_at >= current_date - 1
GROUP BY
DATE(created_at)
This is telling me in full day like 24 records are created but I want to get how many are made in interval of 3 hours

If you want the count for the last three hours of data:
select count(*)
from record
where created_at >= now() - interval '3 hour';
If you want the last day minus 3 hours, that would be 21 hours:
select count(*)
from record
where created_at >= now() - interval '21 hour';
EDIT:
You want intervals of 3 hours for the last 24 hours. The simplest method is probably generate_series():
select gs.ts, count(r.created_at)
from generate_series(now() - interval '24 hour', now() - interval '3 hour', interval '3 hour') gs(ts) left join
record r
on r.created_at >= gs.ts and
r.created_at < gs.ts + interval '3 hour'
group by gs.ts
order by gs.ts;

Related

Take last 3 hours and group by 5 minutes [duplicate]

This question already has answers here:
Postgresql SQL GROUP BY time interval with arbitrary accuracy (down to milli seconds)
(10 answers)
Closed 1 year ago.
I was wondering if you can help me write a query that should just SELECT count(*) but only include data from last 3 hours and group it by 5 minutes.
So I have a table that has a createdts so I have the date there. I just want to see how many entries I have in the last 3 hours, but group COUNT(*) per 5 minutes.
SELECT COUNT(*)
FROM mytable
WHERE createdts >= now()::date - interval '3 hour'
GROUP BY 'every 5 minutes'
Also, what's really important is that the Count(*)'s that are null, get defaulted to 0. I have many windows of time where the data will be null, and having it default to 0 saves a lot of headache later
Use generate_series():
SELECT gs.t, COUNT(t.createdts)
FROM GENERATE_SERIES(now()::date - interval '3 hour', now()::date, interval '5 minute') gs(t)
mytable t
ON t.createdts >= gs.t AND
t.createdts < gs.t + interval '5 minute'
GROUP BY gs.t;

SQL: Select average value of column for last hour and last day

I have a table like below image. What I need is to get average value of Volume column, grouped by User both for 1 hour and 24 hours ago. How can I use avg with two different date range in single query?
You can do it like:
SELECT user, AVG(Volume)
FROM mytable
WHERE created >= NOW() - interval '1 hour'
AND created <= NOW()
GROUP BY user
Few things to remember, you are executing the query on same server with same time zone. You need to group by the user to group all the values in volume column and then apply the aggregation function like avg to find average. Similarly if you need both together then you could do the following:
SELECT u1.user, u1.average, u2.average
FROM
(SELECT user, AVG(Volume) as average
FROM mytable
WHERE created >= NOW() - interval '1 hour'
AND created <= NOW()
GROUP BY user) AS u1
INNER JOIN
(SELECT user, AVG(Volume) as average
FROM mytable
WHERE created >= NOW() - interval '1 day'
AND created <= NOW()
GROUP BY user) AS u2
ON u1.user = u2.user
Use conditional aggregation. Postgres offers very convenient syntax using the FILTER clause:
SELECT user,
AVG(Volume) FILTER (WHERE created >= NOW() - interval '1 hour' AND created <= NOW()) as avg_1hour,
AVG(Volume) FILTER (WHERE created >= NOW() - interval '1 day' AND created <= NOW()) as avg_1day
FROM mytable
WHERE created >= NOW() - interval '1 DAY' AND
created <= NOW()
GROUP BY user;
This will filter out users who have had no activity in the past day. If you want all users -- even those with no recent activity -- remove the WHERE clause.
The more traditional method uses CASE:
SELECT user,
AVG(CASE WHEN created >= NOW() - interval '1 hour' AND created <= NOW() THEN Volume END) as avg_1hour,
AVG(CASE WHEN created >= NOW() - interval '1 day' AND created <= NOW() THEN Volume END) as avg_1day
. . .
SELECT User, AVG(Volume) , ( IIF(created < DATE_SUB(NOW(), INTERVAL 1 HOUR) , 1 , 0) )IntervalType
WHERE created < DATE_SUB(NOW(), INTERVAL 1 HOUR)
AND created < DATE_SUB(NOW(), INTERVAL 24 HOUR)
GROUP BY User, (IIF(created < DATE_SUB(NOW(), INTERVAL 1 HOUR))
Please Tell me about it's result :)

Get value zero if data is not there in PostgreSQL

I have a table employee in Postgres:
Query:
SELECT DISTINCT month_last_date,number_of_cases,reopens,csat
FROM employee
WHERE month_last_date >=(date('2017-01-31') - interval '6 month')
AND month_last_date <= date('2017-01-31')
AND agent_id='analyst'
AND name='SAM';
Output:
But if data is not in table for other month I want column value as 0.
Generate all dates you are interested in, LEFT JOIN to the table and default to 0 with COALESCE:
SELECT DISTINCT -- see below
i.month_last_date
, COALESCE(number_of_cases, 0) AS number_of_cases -- see below
, COALESCE(reopens, 0) AS reopens
, COALESCE(csat, 0) AS csat
FROM (
SELECT date '2017-01-31' - i * interval '1 mon' AS month_last_date
FROM generate_series(0, 5) i -- see below
) i
LEFT JOIN employee e ON e.month_last_date = i.month_last_date
AND e.agent_id = 'analyst' -- see below
AND e.name = 'SAM';
Notes
If you add or subtract an interval of 1 month and the same day does not exist in the target month, Postgres defaults to the latest existing day of that moth. So this works as desired, you get the last day of each month:
SELECT date '2017-12-31' - i * interval '1 mon' -- note 31
FROM generate_series(0,11) i;
But this does not, you'd get the 28th of each month:
SELECT date '2017-02-28' - i * interval '1 mon' -- note 28
FROM generate_series(0,11) i;
The safe alternative is to subtract 1 day from the first day of the next month, like #Oto demonstrated. Related:
Daily average for the month (needs number of days in month)
Here are two optimized ways to generate a series of last days of the month - up to and including a given month:
1.
SELECT (timestamp '2017-01-01' - i * interval '1 month')::date - 1 AS month_last_date
FROM generate_series(-1, 10) i; -- generate 12 months, off-by-1
Input is the first day of the month - or calculate it from a given date or timestamp with date_trunc():
SELECT date_trunc('month', timestamp '2017-01-17')::date AS this_mon1
Subtracting an interval from a date produces a timestamp. After the cast back to date we can simply subtract an integer to subtract days.
2.
SELECT m::date - 1 AS month_last_date
FROM generate_series(timestamp '2017-02-01' - interval '11 month' -- for 12 months
, timestamp '2017-02-01'
, interval '1 mon') m;
Input is the first day of the next month - or calculate it from any given date or timestamp with:
SELECT date_trunc('month', timestamp '2017-01-17' + interval '1 month')::date AS next_mon1
Related:
How do I determine the last day of the previous month using PostgreSQL?
Create list with first and last day of month for given period
Not sure you actually need DISTINCT. Typically, (agent_id, month_last_date) would be defined unique, then remove DISTINCT ...
Be sure to use the LEFT JOIN correctly. Join conditions go into the join clause, not the WHERE clause:
Explain JOIN vs. LEFT JOIN and WHERE condition performance suggestion in more detail
Finally, default to 0 with COALESCE where NULL values are filled in by the LEFT JOIN.
Note that COALESCE cannot distinguish between actual NULL values from the right table and NULL values filled in for missing rows. If your columns are not defined NOT NULL, there may be ambiguity to address.
As I see, you need generate last days of all last 6 months, before certain date. (before "2017-01-31" in this case).
If I correctly understand, then you can use this query, which generates all of these days
SELECT (date_trunc('MONTH', mnth) + INTERVAL '1 MONTH - 1 day')::DATE
FROM
generate_series('2017-01-31'::date - interval '6 month', '2017-01-31'::date, '1 month') as mnth;
You just need LEFT JOIN this query to your existing query, and you get desirable result
Please note that this will returns 7 record (days), not 6.

Generate series of week intervals for given month

In a Postgres 9.1 database, I am trying to generate a series of weeks for a given month but with some constraints. I need all weeks to start on Monday and get cut when they start or end in another month.
Example:
For February, 2013 I want to generate a series like this:
start
------------------------
2013-02-01 00:00:00+00
2013-02-04 00:00:00+00
2013-02-11 00:00:00+00
2013-02-18 00:00:00+00
2013-02-25 00:00:00+00
The query that I have now looks like this:
SELECT GREATEST(date_trunc('week', dates.d),
date_trunc('month',dates.d)) as start
FROM generate_series(to_timestamp(1359676800),to_timestamp(1362095999), '1 week') as dates(d)
This query gets me the first 4 weeks but it's missing the week from the 25th. Is it possible to get the last week?
SELECT generate_series(date_trunc('week', date '2013-02-01' + interval '6 days')
, date_trunc('week', date '2013-02-01' + interval '1 month - 1 day')
, interval '1 week')::date AS day
UNION SELECT date '2013-02-01'
ORDER BY 1;
This variant does not need a subselect, GREATEST or GROUP BY and only generates the required rows. Simpler, faster. It's cheaper to UNION one row.
Add 6 days to the first day of the month before date_trunc('week', ...) to compute the first Monday of the month.
Add 1 month and subtract 1 day before date_trunc('week', ...) to get the last Monday of the month.
This can conveniently be stuffed into a single interval expression: '1 month - 1 day'
UNION (not UNION ALL) the first day of the month to add it unless it's already included as Monday.
Note that date + interval results in timestamp, which is the optimum here. Detailed explanation:
Generating time series between two dates in PostgreSQL
Automation
You can provide the start of the date series in a CTE:
WITH t(d) AS (SELECT date '2013-02-01') -- enter 1st of month once
SELECT generate_series(date_trunc('week', d + interval '6 days')
, date_trunc('week', d + interval '1 month - 1 day')
, interval '1 week')::date AS day
FROM t
UNION SELECT d FROM t
ORDER BY 1;
Or wrap it into a simple SQL function for convenience with repeated calls:
CREATE OR REPLACE FUNCTION f_week_starts_this_month(date)
RETURNS SETOF date AS
$func$
SELECT generate_series(date_trunc('week', $1 + interval '6 days')
, date_trunc('week', $1 + interval '1 month - 1 day')
, interval '1 week')::date AS day
UNION
SELECT $1
ORDER BY 1
$func$ LANGUAGE sql IMMUTABLE;
Call:
SELECT * FROM f_week_starts_this_month('2013-02-01');
You would pass the date for the first day of the month, but it works for any date. You the first day and all Mondays for the following month.
select
greatest(date_trunc('week', dates.d), date_trunc('month',dates.d)) as start
from generate_series('2013-02-01'::date, '2013-02-28', '1 day') as dates(d)
group by 1
order by 1

Best way to get recent date rows? (this week/month)

I wanna get rows in recent date, this week or month, etc. suppose the table has a field named: product_date.
To get the rows in the last month, you could use something like:
SELECT * FROM table WHERE product_date >= DATE_SUB(NOW(), INTERVAL 1 MONTH);
Or for the last week:
SELECT * FROM table WHERE product_date >= DATE_SUB(NOW(), INTERVAL 1 WEEK);
For within the last seven days:
WHERE product_date BETWEEN DATE_ADD(NOW(), INTERVAL -7 DAY) AND NOW()
For the last month:
WHERE product_date BETWEEN DATE_ADD(NOW(), INTERVAL -1 MONTH) AND NOW()
While mopoke's solution gets results from last 30 or 7 days, this gives you results for current month only:
SELECT * FROM table WHERE DATE_FORMAT(product_date, '%c-%Y') = DATE_FORMAT(CURDATE(), '%c-%Y');
Or for current week:
SELECT * FROM table WHERE DATE_FORMAT(product_date, '%u-%Y') = DATE_FORMAT(CURDATE(), '%u-%Y');