Skip operand in condition if column doesn't exists - sql

I have next part of condition:
(COALESCE(some_day, now()) at time zone 'Some/TZ')::date - interval '1 day' + interval '1 day' * day_number
what I need is to skip - interval '1 day' if some_day is null. How I can do this in SQL?

You can put that piece of logic in the COALESCE():
COALESCE(some_day - interval '1 day' ,
now()) at time zone 'Some/TZ'
)::date + interval '1 day' * day_number

Related

Combine multiple selects for statistics generation into on result set

Hey Pros,
I am far away to have good knowledge about SQL, and would ask you to give me some hints.
Currently we aggregate our data with python and I would try to switch this when possible to. (SQL (Postgresql server)
My goal is to have one statment that generate an average for two seperates column's for specific time intervals (1 Hour, 1 Day, 1 Week, Overall) also all events in each period shoud be counted.
I can create 4 single statments for each interval but strugle how to combine this four selects into on result set.
select
count(id) as hour_count,
camera_name,
round(avg("pconf")) as hour_p_conf,
round(avg("dconf")) as hour_d_conf
from camera_events where timestamp between NOW() - interval '1 HOUR' and NOW() group by camera_name;
select
count(id) as day_count,
camera_name,
round(avg("pconf")) as day_p_conf,
round(avg("dconf")) as day_d_conf
from camera_events where timestamp between NOW() - interval '1 DAY' and NOW() group by camera_name;
select
count(id) as week_count,
camera_name,
round(avg("pconf")) as week_p_conf,
round(avg("dconf")) as week_d_conf
from camera_events where timestamp between NOW() - interval '1 WEEK' and NOW() group by camera_name;
select
count(id) as overall_count,
camera_name,
round(avg("pconf")) as overall_p_conf,
round(avg("dconf")) as overall_d_conf
from camera_events group by camera_name;
When possbile the result should look like the data on image
Some hints would be great, thank u
Consider conditional aggregation by moving WHERE logic to CASE statements in SELECT. Alternatively, in PostgreSQL use FILTER clauses.
select
camera_name,
count(id) filter(timestamp between NOW() - interval '1 HOUR' and NOW()) as hour_count,
round(avg("pconf") filter(timestamp between NOW() - interval '1 HOUR' and NOW())) as hour_p_conf,
round(avg("dconf") filter(timestamp between NOW() - interval '1 HOUR' and NOW())) as hour_d_conf,
count(id) filter(timestamp between NOW() - interval '1 DAY' and NOW()) as day_count,
round(avg("pconf") filter(timestamp between NOW() - interval '1 DAY' and NOW())) as day_p_conf,
round(avg("dconf") filter(timestamp between NOW() - interval '1 DAY' and NOW())) as day_d_conf,
count(id) filter(timestamp between NOW() - interval '1 WEEK' and NOW()) as week_count,
round(avg("pconf") filter(timestamp between NOW() - interval '1 WEEK' and NOW())) as week_p_conf,
round(avg("dconf") filter(timestamp between NOW() - interval '1 WEEK' and NOW())) as week_d_conf,
count(id) as overall_count,
round(avg("pconf")) as overall_p_conf,
round(avg("dconf")) as overall_d_conf
from camera_events
group by camera_name;
The simplest way is to join them. For example:
select
coalesce(h.camera_name, d.camera_name, w.camera_name) as camera_name
h.hour_count, h.hour_p_conf, h.hour_d_conf
d.day_count, d.day_p_conf, d.day_d_conf
w.week_count, w.week_p_conf, w.week_d_conf
from (
-- hourly query here
) h
full join (
-- daily query here
) d on d.camera_name = h.camera_name
full join (
-- weekly query here
) w on w.camera_name = coalesce(h.camera_name, d.camera_name)

replace multiple queries with a single query which will give the same result

The queries are :
select date_trunc('month', now()) - interval '1 month' as prev_month_first_date
select (date_trunc('month', now())::date-1 - interval '0 days') as prev_month_last_date;
select date_trunc('month', now()) as current_month_first_date;
SELECT date_trunc('year', now()) as current_year_first_date;
SELECT date_trunc('quarter', now()) as current_quarter_first_date;
can I form a single query with all these queries? I am working on postgresql.
If you are looking for separate columns, just use a single select with multiple expressions:
select date_trunc('month', now()) - interval '1 month' as prev_month_first_date,
(date_trunc('month', now())::date-1 - interval '0 days') as prev_month_last_date,
date_trunc('month', now()) as current_month_first_date,
date_trunc('year', now()) as current_year_first_date,
date_trunc('quarter', now()) as current_quarter_first_date;

How to select all rows created in the last hour?

My query is
SELECT * FROM email_operation WHERE cdate = CURRENT_DATE and ctime >= (NOW() - INTERVAL '1 hour' )
to select all rows created at the last hour but it doesn't work. It throws an error at >=
SELECT * FROM email_operation WHERE cdate = CURRENT_DATE and ctime >= (NOW() - INTERVAL '12 hour' )
> ERROR: operator does not exist: time without time zone >= timestamp with time zone
LINE 1: ..._operation WHERE cdate = CURRENT_DATE and ctime >= (NOW() ...
^
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
> Time: 0.001s
Apparently ctime is a time column, so you can't compare it with a timestamp (which is what the error message tells you). You need to use current_time instead:
and ctime >= current_time - interval '1 hour'
Note that this won't work properly around midnight.
It seems you have split up date and time into two columns (which is a bad idea), but you can combine them to compare a timestamp:
and cdate + ctime >= current_timestamp - interval '1 hour'
If you want results in the past hour and you have separated the date and time components, then one method is:
WHERE (cdate + ctime) >= NOW() - INTERVAL '1 hour'
Note that this will even work between midnight and 1:00 a.m. Sadly, it probably won't use indexes. That might be an issue if you have lots of data. Here is one method that will at least use an index on cdate:
WHERE cdate IN (CURRENT_DATE, CURRENT_DATE - INTERVAL '1 day') AND
( (cdate + ctime) >= NOW() - INTERVAL '1 hour' )
where (cdate + ctime)::timestamp(0) >= (now() - interval '1 hour')

Set-returning functions are not allowed in CASE in postgreSQL

I am trying to run this query was able to till some time ago. I don't know what went wrong and I started getting this error now?
Your database returned: ERROR: set-returning functions are not allowed in CASE Hint: You might be able to move the set-returning function into a LATERAL FROM item.
My query:
SELECT distinct
(CASE
WHEN {PERIOD} = 'Previous Quarter' AND pto.pto_start_date < (date_trunc('quarter', now() - INTERVAL '1 month') + INTERVAL '1 month')::date AND pto.pto_end_date >= (date_trunc('quarter', now() - INTERVAL '1 month') + INTERVAL '1 month')::date
THEN generate_series(pto.pto_start_date, pto.pto_end_date, '2 day'::interval)
WHEN {PERIOD} = 'Current Quarter' AND pto.pto_start_date < (date_trunc('quarter', now() - INTERVAL '1 month') + INTERVAL '1 month')::date AND pto.pto_end_date >= (date_trunc('quarter', now() - INTERVAL '1 month') + INTERVAL '1 month')::date
THEN generate_series(pto.pto_start_date, pto.pto_end_date, '1 day'::interval)
ELSE
generate_series(pto.pto_start_date, pto.pto_end_date, '1 day'::interval)
END) AS dt
FROM cust_pto pto
Start dates and end Dates:
What has gone wrong?
Why you're getting the error now: you upgraded to postgres 10. Set returning functions are no longer allowed.
What to do: there is more than one way to accomplish what you're trying to do. For the sake of keeping it as close as possible to your original query, all you have to do is put your CASE statement inside generate_series:
SELECT distinct generate_series(
pto.pto_start_date,
pto.pto_end_date,
CASE
WHEN {PERIOD} = 'Previous Quarter' AND pto.pto_start_date < (date_trunc('quarter', now() - INTERVAL '1 month') + INTERVAL '1 month')::date AND pto.pto_end_date >= (date_trunc('quarter', now() - INTERVAL '1 month') + INTERVAL '1 month')::date THEN
'2 day'::interval
ELSE
'1 day'::interval
END
) AS dt
FROM cust_pto pto

Can I use a modulo to specify an interval in postgres?

I have to list items that have not been updated for a multiple of two years after their last update. This is to run as a cron job once a day.
I know I can do this with something ugly like:
SELECT art_id, art_update FROM items
WHERE art_update = now()::date - interval '2 years'
OR art_update = now()::date - interval '4 years'
OR art_update = now()::date - interval '6 years'
OR art_update = now()::date - interval '8 years'
OR art_update = now()::date - interval '10 years';
Is there any way to avoid this by checking for a modulo interval? Or some other generalised way to express this?
select art_id, art_update
from items
where art_update in (
(now() - interval '2 years')::date,
(now() - interval '4 years')::date,
(now() - interval '6 years')::date,
(now() - interval '8 years')::date,
(now() - interval '10 years')::date
);
or
select art_id, art_update
from items
where art_update in (
select d::date
from generate_series (
now() - interval '2 years',
now() - interval '10 years',
- interval '2 years'
) d(d)
);
http://www.postgresql.org/docs/current/static/functions-srf.html
You can try this.
SELECT art_id, art_update
FROM items
Where int4(date_part('year', art_update)) % 2 = 0;
You can generate a series of dates at 2 year intervals going back from today (to 10 years ago in the below) and join this back to your table:
SELECT i.art_id, i.art_update
FROM items i
INNER JOIN generate_series(2, 10, 2) s (years)
ON i.art_update = now()::date - interval '1 years' * s.years;
Example on SQL Fiddle
N.B This appears to be marginally faster if you generate the dates in the series, rather than numbers:
SELECT i.art_id, i.art_update
FROM items i
INNER JOIN generate_series(now() - interval '10 years',
now() - interval '2 years',
interval '2 years') d (d)
ON art_update = d.d::date;