Update time only if not a minute passed - postgresql

I have created the next table.
-- TABLE user_time
user_id integer PRIMARY KEY,
prev_time TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
total_time INTERVAL DEFAULT interval '0 second'
I have to add an interval value to the total_time, e.g.
total_time = total_time + NOW() - prev_time
only if not a minute passed since prev_time (so, less than 1 minute passed) in a single query.
The next construction is about what I want but it's wrong:
UPDATE user_time SET total_time = total_time +
(
SELECT NOW() - prev_time incinterval,
CASE
WHEN incinterval < interval '1 minute' THEN incinterval
ELSE interval '0 second'
END
FROM user_time WHERE user_id=6
)
Firstly the SELECT is wrong, PostgreSQL does not recognize incinterval in the CASE construction. Secondly there is the first extra column in SELECT which creates a pseudo name.
Do you have an idea how to correct the query OR
Is it the common practice to increment total time with the condition and store it to a database with a single query?

You can not expect Postgres to respect your alias like this inside a SELECT. Here's the way to go:
UPDATE user_time SET total_time = total_time +
(
SELECT
CASE
WHEN NOW() - prev_time < interval '1 minute'
THEN NOW() - prev_time
ELSE interval '0 second'
END
FROM user_time WHERE user_id=6
)
Or to use your alias:
UPDATE user_time SET total_time = total_time +
(
SELECT
CASE
WHEN incinterval < interval '1 minute'
THEN incinterval
ELSE interval '0 second'
END
FROM(
SELECT
NOW() - prev_time incinterval
FROM user_time WHERE user_id=6
) foo
)
Edit after comment:
Simply add , prev_time = NOW() after the last parenthesis in any option you choose from those above.

The deadlock issue can be resolved by doing the analysis post hoc. Just log the queries and "roll up" clusters that meet your interval spec. This allows for variable-length intervals, which can be very useful for sites that provide dense content.
The "post hoc" process can/should run periodically in overlapping ranges to catch longer intervals.
There is an relevant example of a generalizable temporal clustering query using analytic functions from about 8 years ago on asktom.oracle.com.

Related

Postgresql - query to get difference in data count

I have two tables, today's_table and yeterday's_table.
I need to compare the data for an interval of 15 mins at exact same times for today and yesterday.
For example, for below data let's I need to check from 00:00:00 and 00:15:00 on 20201202 and 20201202. So difference should come out as '3' since the yesterday's_table has 8 records and today's_table has 5 records.
today's_table:
Yesterday's table:
I tried something like; (consider now() is 00:15:00)
select count(*) from yeterday's_table where time between now() - interval "24 hours" and now() - interval "23 hours 45 mins"
minus
select count(*) from today's_table where time = now() - interval "15 minutes";
is there any other way to do this?
You can easily do this with subqueries:
SELECT b.c - a.c
FROM (select count(*) as c from yeterdays_table where time between now() - interval '24 hours' and now() - interval '23 hours 45 mins') a,
(select count(*) as c from todays_table where time = now() - interval '15 minutes') b;
Bear in mind you need to single-quote your intervals, and your table names cannot have quotes in them.

How to calculate how many intervals at given daterange? simpler version

I can write:
select count(*) from generate_series(
'2019-03-01'::date, '2019-05-01'::date,
interval '3 day 1 hour'
)
-- exclude upper boundary
where generate_series <> date '2019-05-01'::date;
Is there a way to do it simpler? like:
daterange( '2019-03-01', '2019-05-01' ) / interval '3 day 1 hour'
You can use
EXTRACT(epoch FROM some_interval)
to get an interval's duration in seconds.
You could use that as follows:
SELECT EXTRACT(epoch FROM '2019-05-01'::timestamptz - '2019-03-01'::timestamptz)
/ EXTRACT(epoch FROM interval '3 day 1 hour');
Note that this will only give correct answers for intervals that are measured in days or lesser units; for months and more you have to go with your original solution.

PostgreSQL - Difference between Current Date and a Date from Database

I'm trying to make a SELECT query which will compare current time with time at database. For example in database there is a record '2018-02-07 12:00:00' and I wanna compare it to current time. If current time is '2018-02-07 11:00:00', record '2018-02-07 12:00:00' should be visible in results. It should compare two dates and shows only those who are 1h before or after current time.'
Tried something like this:
SELECT * FROM events WHERE age(current_date, event_date) < '1 hour';
or
SELECT * FROM events WHERE event_date > (now() - INTERVAL '1 hour');
those that are 1h before or after current time
Wouldn't the logic look like this?
SELECT e.*
FROM events e
WHERE e.event_date > now() - INTERVAL '1 hour' AND
e.event_date < now() + INTERVAL '1 hour'

Find difference between timestamps in amount of custom intervals in PostgreSQL

I would like to find difference between two timestamps (with timezone) in amount of custom intervals. So function should be like custom_diff(timestamptz from, timestamptz to, interval custom).
Keep in mind, that it is not equivalent to (to-from)/custom (custom_diff('2016-08-01 00:00:00','2016-09-01 00:00:00','1 day') is exactly 31, but ('2016-08-01 00:00:00','2016-09-01 00:00:00')/'1 day')='1 month'/'1 day' and is ambiguous).
Also I understand that in general there is no exact result of such operation (custom_diff('2016-08-01 00:00:00','2016-09-01 00:00:00','1 month 1 day') so it is possible to have group of function (round-to-nearest, round-to-lower, round-to-upper and truncating, all of them should return integer number).
Is there any standard/common way for such calculation in PostgreSQL (PL/pgSQL)? My main interesting is round-to-nearest function.
The best way I have invented is to iteratively add/substract interval custom to/from timestamptz from and compare with timestamptz to. Also it can be optimized by initially finding approximate result (for example divide [difference in seconds between timestamps] for [approximation of interval custom in seconds]) to reduce amount of iterations.
UPD 1:
Why
SELECT EXTRACT(EPOCH FROM (timestamp '2016-08-01 10:00'
- timestamp '2016-08-01 00:00'))
/ EXTRACT(EPOCH FROM interval '1 day');
is a wrong solution: lets try yourself:
SELECT EXTRACT(EPOCH FROM ( TIMESTAMPTZ '2016-01-01 utc' -
TIMESTAMPTZ '1986-01-01 utc' ))
/ EXTRACT(EPOCH FROM INTERVAL '1 month');
Result is 365.23.... Then check result:
SELECT ( TIMESTAMPTZ '1986-01-01 utc' + 365 * INTERVAL '1 month' )
AT TIME ZONE 'utc';
Result is 2016-06-01 00:00:00.000000. Of cause 365 is wrong result, because timestamps in this example describe exactly 30 years and in any year always exactly 12 months, so right answer is 12*30=360.
UPD 2:
My solution is
CREATE OR REPLACE FUNCTION custom_diff(
_from TIMESTAMPTZ, _to TIMESTAMPTZ, _custom INTERVAL, OUT amount INTEGER)
RETURNS INTEGER
LANGUAGE plpgsql
AS $function$
DECLARE
max_iterations INTEGER :=10;
t INTEGER;
BEGIN
amount:=0;
WHILE max_iterations > 0 AND NOT (
extract(EPOCH FROM _to) <= ( extract(EPOCH FROM _from) + extract(EPOCH FROM _from + _custom) ) / 2
AND
extract(EPOCH FROM _to) >= ( extract(EPOCH FROM _from) + extract(EPOCH FROM _from - _custom) ) / 2
) LOOP
-- RAISE NOTICE 'iter: %', max_iterations;
t:=EXTRACT(EPOCH FROM ( _to - _from )) / EXTRACT(EPOCH FROM _custom);
_from:=_from + t * _custom;
amount:=amount + t;
max_iterations:=max_iterations - 1;
END LOOP;
RETURN;
END;
$function$
but I does not sure that it is correct and still waiting for sugestion about existing/common solution.
You can get exact result after extracting the epoch from both intervals:
SELECT EXTRACT(EPOCH FROM (timestamp '2016-08-01 10:00'
- timestamp '2016-08-01 00:00'))
/ EXTRACT(EPOCH FROM interval '1 day'); -- any given interval
If you want rounded (truncated) result, a simple option is to cast both to integer. Integer division cuts off the remainder.
SELECT EXTRACT(EPOCH FROM (ts_to - ts_from))::int
/ EXTRACT(EPOCH FROM interval '1 day')::int; -- any given interval
You can easily wrap the logic into a IMMUTABLE SQL function.
You are drawing the wrong conclusions from what you read in the manual. The result of a timestamp subtraction is an exact interval, storing only days and seconds (not months). So the result is exact. Try my query, it isn't "ambiguous".
You can avoid involving the data type interval:
SELECT EXTRACT(EPOCH FROM ts_to) - EXTRACT(EPOCH FROM ts_from))
/ 86400 -- = 24*60*60 -- any given interval as number of seconds
But the result is the same.
Aside:
"Exact" is an elusive term when dealing with timestamps. You may have to take DST rules and other corner cases of your time zone into consideration. You might convert to UTC time or use timestamptz before doing the math.

How to convert interval to timestamp with time zone with postgresql?

I want to perform this simple query :
SELECT
pid,
MIN(interval '5 minutes' - current_timestamp - state_change)
FROM
pg_stat_activity
AND
current_timestamp - state_change <= interval '5 minutes'
GROUP BY
pid
ORDER BY
2 ASC
LIMIT 1;
But PG complains that there is no operator between interval and timestamp with time zone.
How can I convert my interval to timestamp ?
The impossible conversion is not the problem, it's a matter of evaluation order. Without changing the order of arguments you may write it with parentheses:
interval '5 minutes' - (current_timestamp - state_change)
and it would work since interval - interval is supported. Without the parentheses it doesn't work since interval - timestamp would be evaluated first and it's not implemented.
As a sidenote, since pid is unique in pg_stat_activity, the GROUP BY pid and MIN should be suppressed, as in:
SELECT
pid,
interval '5 minutes' - (current_timestamp - state_change)
FROM
pg_stat_activity
WHERE
current_timestamp - state_change <= interval '5 minutes'