How to implement time tolerance in SQL for general data within a column? - postgresql

SUMMARY: I have two data tables: table1 and table2. I want to join them in the following way:
- a unique id value that is the same within both tables
- ALSO, there is a time value in both tables that I need to be within a certain vicinity of one another (e.g. 30 seconds)
- However, the data in both tables is within YYYY-MM-DD HH-MM-SS form
I have tried to do the following:
SELECT * FROM table1 AS t1
LEFT OUTER JOIN table2 t2
ON t1.id = t2.id
AND t2.message_time BETWEEN t1.time + INTERVAL '20 seconds' AND t1.time - INTERVAL '20 seconds'
but it keeps printing out the same row over and over again when I am looping through the result in psycopg2. Is there a better way to do this? Is the query referring to the respective t1.properly?

You need to switch the BETWEEN parameters:
BETWEEN smaller_value AND greater_value
So your join condition needs to be:
ON t1.id = t2.id
AND t2.message_time BETWEEN t1.message_time - interval '20 seconds' AND t1.message_time + interval '20 seconds'
demo:db<>fiddle

Related

Merge 2 queries into 1 query

I have 2 queries which i need to merge into 1, they are getting data from the same tables but difference is minor between them
I need to merge this queries:
select sum(balance_delta) as "Not Rental"
from h_driver_balance
where
changed between (select current_date - interval '1 days') and (select current_date)
and driver_balance_id in (select id
from driver
where driver_ds_account_id = 16
and callsign not like '0%')
and comment like 'Refill on the terminal 1%';
and
select sum(balance_delta) as "Rental"
from h_driver_balance
where
changed between (select current_date - interval '1 days') and (select current_date)
and driver_balance_id in (select id
from driver
where driver_ds_account_id = 16
and callsign like '0%')
and comment like 'Refill on the terminal 1%';
I've tried to do WITH and UNION, but I seem not to understand something.
If you want two columns, you can do that with a single query and conditional aggregation:
select sum(b.balance_delta) filter (where d.callsign not like '0%') as "Not Rental",
sum(b.balance_delta) filter (where d.callsign like '0%') as "Rental"
from h_driver_balance b
join driver d on d.id = b.driver_balance_id
where b.changed between current_date - interval '1 days' and current_date
and d.driver_ds_account_id = 16
and b.comment like 'Refill on the terminal 1%';

Postgres SQL - How to create a dynamic date variable

I want my query to have a dynamic date. The way it is written now, I would have to manually change the date every time. Please see the following as an example:
(select*
from table2
where table2.begin_timestamp::date = '2015-04-01')as start
left outer join
(Select *
from table 1
where opened_at::date >= ('2015-04-01' - 15)
and opened_at::date <= '2015-04-01’)
I don't want '2015-04-01' to be hard-coded. I want to run this query over and over for a series of dates.
Using normal joins, you can do this in an on clause or where clause but not inside the subquery. That leads to logic like this:
from (select*
from table2
) start left outer join
table 1
on opened_at::date >= table2.begin_timestamp::date - interval '15 day' and
opened_at::date <= table2.begin_timestamp::date
I'm not a postgres developer but I think you can adapt a technique from the sql server world called "tally tables".
Esentially your goal is to join day d and the window of days that are at most 15 days greater than it.
You can use something like
SELECT * FROM generate_series('2015-04-01'::timestamp,
'2015-04-30 00:00', '1 days');
To generate a date sequence and from there you can write something like
select *
from table a
join generate_series('2015-04-01'::timestamp,'2015-04-30','1 days') s(o)
on a.begin_timestamp::date = s.o
join table2 b
on a.opened_at>= b.begin_timestamp::date - interval '15 days'
and opened_at::date <= table2.begintimestamp::date
Essentially, instead of looping you use a series of the dates between the beginning of the interval and the end of the range to produce the results you are after.

Creating 'Empty' Records for Days of the Month Without Records

I have a very simpl postgres (9.3) query that looks like this:
SELECT a.date, b.status
FROM sis.table_a a
JOIN sis.table_b b ON a.thing_id = b.thing_id
WHERE EXTRACT(MONTH FROM a.date) = 06
AND EXTRACT(YEAR FROM a.date) = 2015
Some days of the month of June do not exist in table_a and thus are obviously not joined to table_b. What is the best way to create records for these not represented days and assign a placeholder (e.g. 'EMPTY') to their 'status' column? Is this even possible to do using pure SQL?
Basically, you need LEFT JOIN and it looks like you also need generate_series() to provide the full set of days:
SELECT d.date
, a.date IS NOT NULL AS a_exists
, COALESCE(b.status, 'status_missing') AS status
FROM (
SELECT date::date
FROM generate_series('2015-06-01'::date
, '2015-06-30'::date
, interval '1 day') date
) d
LEFT JOIN sis.table_a a USING (date)
LEFT JOIN sis.table_b b USING (thing_id)
ORDER BY 1;
Use sargable WHERE conditions. What you had cannot use a plain index on date and has to default to a much more expensive sequential scan. (There are no more WHERE conditions in my final query.)
Aside: don't use the basic type name (and reserved word in standard SQL) date as identifier.
Related (2nd chapter):
PostgreSQL: running count of rows for a query 'by minute'

How do I SELECT a number to put into INTERVAL as part of date addition function in PostgreSQL

I have a table which contains a timezone offset, +1, -7, +5 etc.
I have another, joinable, table which contains logging information including a datetime.
I would like to select out the datetime from the second table with the offset added on to it using for example, INTERVAL '1 hours', INTERVAL '-7 hours' etc.
My psuedocode would be something like:
SELECT l.status, l.inserted + INTERVAL (coalesce(timezone_offset, '0')||' hours' ) AS inserted FROM log l
LEFT OUTER JOIN users u on u.usersid = l.usersid
LEFT OUTER JOIN companies c on c.companiesid = u.companiesid
WHERE l.usersid=?
This doesn't work, but I can't figure out how to make it work in PostgreSQL 8.3.
Any ideas?
It turns out you can multiply an INTERVAL by a number. So if I create an INTERVAL of '1 hours' and multiply that by the stored offset, that will work, yielding:
SELECT l.status, l.inserted + (INTERVAL '1 hours' * coalesce(timezone_offset, '0')) FROM log as l
LEFT OUTER JOIN users u on u.usersid = l.usersid
LEFT OUTER JOIN companies c on c.companiesid = u.companiesid
WHERE l.usersid=?
et voila!
You can also use concatenation, so (l.inserted || ' hours')::INTERVAL will also work.

PostgreSQL: Add Interval to Timestamp from column value

I need to add minutes coming from an integer column with a timestamp to compare to another column.
Here's an example:
SELECT t1.id_liame, t1.id_table, t1.periodicidade , t3.data_extracao,
CASE WHEN(NOW() < (e.data_extracao + INTERVAL t1.periodicidade || '
MINUTES'))
THEN 'yes' ELSE 'no' END
FROM table1 as t1 LEFT JOIN liame_has_extracao as t2 USING(id_liame)
LEFT JOIN extracao as t3 USING(id_extracao)
l.periodicidade is integer (minutes)
I need to verify if data_extracao(timestamp) is greater then NOW() + l.periodicidade(integer - minutes).
How can i do it?
You can write your query like this:
SELECT
t1.id_liame,
t1.id_table,
t1.periodicidade,
t3.data_extracao,
CASE
WHEN(NOW() < (t3.data_extracao + (INTERVAL '1 min' * t1.periodicidade)))
THEN 'yes' ELSE 'no'
END
FROM table1 AS t1
LEFT JOIN liame_has_extracao AS t2 USING(id_liame)
LEFT JOIN extracao AS t3 USING(id_extracao)
As you can see, you can multiply intervals with integers so with INTERVAL '1 minute' you define your base unit and multiply your actual time interval.
Hope that helps