Postgres query certain overlapping times on weekdays - postgresql

Imagine you have a set of records for a certain period of time, for example produced products in a month.
Now you want to know how many of those products have been produced at 22 to 3 on Wednesdays and Fridays.
To get all products let's assume the query looks like:
SELECT count(id)
FROM products
WHERE timestamp BETWEEN to_timestamp($startOfMonth) AND to_timestamp($endOfMonth)
where $startOfMonth and $endOfMonth are placeholder values for some integer timestamps.
To get products of a certain time we can make use of "BETWEEN TIME" and add it to the query:
SELECT count(id)
FROM products
WHERE timestamp BETWEEN to_timestamp($startOfMonth) AND to_timestamp($endOfMonth)
AND CAST(timestamp AS time) BETWEEN TIME '3:00' AND TIME '22:00'
Next thing we do is adding the exclusive days we want to query:
SELECT count(id)
FROM products
WHERE timestamp BETWEEN to_timestamp($startOfMonth) AND to_timestamp($endOfMonth)
AND CAST(timestamp AS time) BETWEEN TIME '3:00' AND TIME '22:00'
AND EXTRACT(dow FROM timestamp) IN (3,5)
Everything works fine.
But now I want to query a time overlapping a day:
SELECT count(id)
FROM products
WHERE timestamp BETWEEN to_timestamp($startOfMonth) AND to_timestamp($endOfMonth)
AND CAST(timestamp AS time) BETWEEN TIME '22:00' AND TIME '12:00'
AND EXTRACT(dow FROM timestamp) IN (3,5)
and I don't receive any results.
How to run this query if the starting time is on another day than the ending time?
Thank you ;-)

BETWEEN TIME '3:00' AND TIME '22:00' seems to be the opposite of what you're asking in the question ("produced at 22 to 3").
I think you'll just have to split the time range and use an OR condition, since it crosses midnight. Assuming that "22 to 3" on a Wednesday, you actually mean "22 to midnight" on a Wednesday and "midnight to 3am" on a Thursday (same for Friday/Saturday)
You won't even need BETWEEN, since the lower/upper bounds will be midnight, when the day changes. Something like this should work:
SELECT count(id)
FROM products
WHERE timestamp BETWEEN to_timestamp($startOfMonth) AND to_timestamp($endOfMonth)
AND ((CAST(timestamp AS time) >= TIME '22:00'
AND EXTRACT(dow FROM timestamp) IN (3,5))
OR (CAST(timestamp AS time) < TIME '03:00'
AND EXTRACT(dow FROM timestamp) IN (4,6)))
You may need to adapt this a little if you want to include the timestamps that are on the first Thursday or Saturday of the month (before 3am) into the count for the previous month (if it makes sense for your report): a quick way to do this is to adapt the integer timestamp you provide $endOfMonth to be at 3am on the first day of the following month.
(As a side note, I'd avoid using timestamp as a column name, since it's also a type and can cause confusion.)

Perhaps an easier way is to restate the times in question. That is instead of working with TIMEs a 22:00 and 03:00 consider start time as date+22hours and end time as date+27 hours.(duration of 5 hours from 22:00 - 03:00) Then process with the full timestamp.
SELECT count(id)
FROM products
WHERE timestamp BETWEEN to_timestamp($startOfMonth) AND to_timestamp($endOfMonth)
AND EXTRACT(dow FROM timestamp) IN (3,5)
and timestamp between date_trunc('day', timestamp) + interval '22 hours'
and date_trunc('day', timestamp) + interval '27 hours';
Note: I echo #Bruno on not using timestamp as a column (or any DB object) name. While it is not a Postgres reserved word, Postgres restricts is use and it is a SQL Standard reserved word.

Related

Extracting Hour and Minutes In RedShift Within Same Function

I am trying to find rows that came in before 7:30 pm (as part of a CASE WHEN). The below just has it at 8. Any idea how to get this to be 7:30?
extract(dayofweek from convert_timezone('EST', call_start_time)) in (6) and
extract(hour from convert_timezone('EST', call_start_time))
>=8 and extract(hour from convert_timezone('EST', ${TABLE}.call_start_time)) <20
It looks like you want to test if the call_start_time is after 7:30pm EST on ANY day, right?
If so I think you just want to create an interval between the call_start_time and the beginning of that day - call_start_time - date_trunc('day', call_start_time). (Or if you like trunc(timestamp) will give you the date.) Then compare this to the time of day value you want - interval '13 hours 30 minutes'. (13 hours is 19:00 minus 6 hour time zone difference.)
Have I read you question / intent correctly?
====================================
It looks like there is a need to provide a code example. The simplest way to share this is with a sqlfiddle but that means that the syntax will be Postgresql not native Redshift. For the parts of the SQL in question this isn't a change - working with timezones is different but that is handled by an area not in the "interval" calculations.
With prep as (
select *, (t at time zone 'UTC') at time zone 'EST' as est_t,
date_trunc('day', (t at time zone 'UTC') at time zone 'EST') as est_t_date
from fred)
select n, t from prep
where extract(dow from (t at time zone 'UTC') at time zone 'EST') in (6)
and est_t between est_t_date + interval '8 hours'
and est_t_date + interval '19 hours 30 minutes'
;
Live code example at http://sqlfiddle.com/#!17/5236b/2

Column of type "timestamp(6) with timezone" and "current time" difference in minutes

I have Oracle 12c DB table and one of it's column utc_timestamp is of type
UTC_TIMESTAMP TIMESTAMP(6) WITH TIME ZONE
It stores timestamp in UTC while current_timestamp and systimestamp both gives timestamp in different timezones.
How can I get time difference in MAX(utc_timestamp) and current_timestamp in minutes ignoring time difference due to different time zones.
For example:
select current_timestamp from dual;
Gives=> 23-AUG-17 04.43.16.253931000 PM AMERICA/CHICAGO
select systimestamp from dual;
Gives=> 23-AUG-17 05.43.16.253925000 PM -04:00
select max(UTC_TIMESTAMP) from table_name;
Gives=> 23-AUG-17 09.40.02.000000000 PM +00:00
For above condition when I run SQL to check time difference between in MAX(utc_timestamp) and current_timestamp I should get number 3.
I think I need something like:
select (extract(minute from current_timestamp) - extract(minute from max(UTC_TIMESTAMP)) * 1440) AS minutesBetween from table_name;
But different timezones are messing it up and I get negative number like -4317. This might be correct as current_timestamp will be higher than max(utc_timestamp) being in CST. So I tried:
select (extract(minute from CAST(current_timestamp as TIMESTAMP(6) WITH TIME ZONE)) - extract(minute from max(UTC_TIMESTAMP)) * 1440) AS minutesBetween from table_name;
This SQL runs without error but producing a big negative number like -83461. Please help me find what am I doing wrong.
You really have two problems here.
One is to convert CURRENT_TIMESTAMP to UTC. That is trivial:
select CURRENT_TIMESTAMP AT TIME ZONE 'UTC' from dual [.....]
(use the AT TIME ZONE clause https://docs.oracle.com/cd/B19306_01/server.102/b14225/ch4datetime.htm#i1007699)
The other is that the difference between two timestamps is an interval, not a number.
select current_timestamp at time zone 'UTC'
- to_timestamp_tz('24-AUG-17 04.00.00.000 AM UTC', 'dd-MON-yy hh.mi.ss.ff AM TZR')
from dual;
produces something like
+00 00:02:39.366000
which means + (positive difference) 00 days, 00 hours, 02 minutes, 39.366 seconds.
If you just want the minutes (always rounded down), you may wrap this whole expression within extract( minute from < ...... > ). Be aware though that the answer will still be 2 (minutes) even if the difference is five hours and two minutes. It is probably best to leave the result in interval data type, unless you are 100% sure (or more) that the result is always less than 1 hour.

PostgreSQL select a fixed time intervals from a range of days defined in timestamp

The time data in my database is stored using timestamp. I want to select a fixed time intervals from a range of days. For example, select the tuples that has timestamp that satisfies: its time is between 2pm and 3pm, and its date is between 2015-01-01 and 2015-01-31.
SELECT * FROM data WHERE ????time???? ;
In plain English select the data that are recorded between 2pm and 3pm for January.
Any suggestions?
Try
SELECT * FROM data
WHERE time_column BETWEEN date '2015-01-01' AND date '2015-01-31'
AND extract( hour from time_column ) BETWEEN 14 AND 15 ;

Getting a weird result for ISO Date in Postgresql

I have a table task_details. I need to select its weekly date from this table. I have used ISO Year & ISO Week to extract the weekly dates from this table as I want to extract its week number as 53 if its Dec,2015 then on the days 28 Dec,29 Dec,30 Dec,31 Dec and 1 Jan '16,2 Jan '16 since it should not separate to two different weeks for these sort of dates. The query I have used for ISO Year & ISO Week is given below.
select
name, id,
to_date(week || ' ' || yr, 'IW IYYY') week_date,
sum(worked_hours) worked_hours_per_week,
week, yr
from (
select
name, id,
newdate, hours worked_hours,
to_number(to_char(newdate, 'IW'), '99') week,
extract( year from newdate) yr
from task_details t
) sub
where worked_hours > 0
group by name, id, to_date(week || ' ' || yr, 'IW IYYY'), week, yr
order by yr, week
It is working fine for the weeks but then I am getting this weird result for one date for a record in the table. The table doesn't have the data for the year 2017.Also, the yr column is showing 2016 which is as desired but then the newdate column and week column is giving weird result. Why is this happening? How do I fix this ?
Here is the SQL fiddle for it :http://sqlfiddle.com/#!15/53abf/1
You should not mix week with year in the extract function as year is for the Gregorian calendar rather than the special ISO calendar.
See section 9.9.1 and comments about week.
to_number(to_char(newdate, 'IW'), '99') is effectively extract(week from newdate)
Changing the yr column to be extract(isoyear from newdate) solves your problem.
Adjusted SQL Fiddle
#gwaigh already cleared up your confusion of Gregorian and ISO year.
However, your query would be simpler and faster with date_trunc() to get the first day of each week:
SELECT name, id
, date_trunc('week', newdate)::date AS week_date
, extract(week FROM newdate)::int AS week
, extract(isoyear from newdate)::int AS isoyr -- must be ISO year to match
, sum(hours) AS worked_hours_per_week
FROM task_details
WHERE hours > 0
GROUP BY name, id, week_date, week, yr
ORDER BY week_date;
Also simplified your query.
SQL Fiddle.
Either way, if you work with timestamptz then year, week or date depend on your current timezone setting. (It can be a different year, depending on where you are right now.)

Select Data over time period

I'm a bit of newbie when it comes to postgres, so bear with me a wee bit and i'll see if i can put up enough information.
i insert weather data into a table every 10 mins, i have a time column that is stamped with an epoch date.
I Have a column of the last hrs rain fall, and every hr that number changes of course with the running total (for that hour).
What i would like to do is skim through the rows to the end of each hour, and get that row, but do it over the last 4 hours, so i would only be returning 4 rows say.
Is this possible in 1 query? Or should i do multiple queries?
I would like to do this in 1 query but not fussed...
Thanks
Thanks guys for your answers, i was/am a bit confused by yours gavin - sorry:) comes from not knowing this terribly well.
I'm still a bit unsure about this, so i'll try and explain it a bit better..
I have a c program that inserts data into the database every 10 mins, it reads the data fom a device that keeps the last hrs rain fall, so every 10 mins it could go up by x amount.
So i guess i have 6 rows / hr of data.
My plan was to go back (in my php page) every 7, which would be the last entry for every hour, and just grab that value. Hence why i would only ever need 4 rows.. just spaced out a bit!
My table (readings) has data like this
index | time (text) | last hrs rain fall (text)
1 | 1316069402 | 1.2
All ears to better ways of storing it too :) I very much appreciate your help too guys thanks.
You should be able to do it in one query...
Would something along the lines of:
SELECT various_columns,
the_hour,
SUM ( column_to_be_summed )
FROM ( SELECT various_columns,
column_to_be_summed,
extract ( hour FROM TIME ) AS the_hour
FROM readings
WHERE TIME > ( NOW() - INTERVAL '4 hour' ) ) a
GROUP BY various_columns,
the_hour ;
do what you need?
SELECT SUM(rainfall) FROM weatherdata WHERE time > (NOW() - INTERVAL '4 hour' );
I don't know column names but that should do it the ones in caps are pgsql types. Is that what you are after?
I am not sure if this is exactly what you are looking for but perhaps it may serve as a basis for adaptation.
I often have a requirment for producing summary data over time periods though I don't use epoch time so there may be better ways of manipulating the values than I have come up with.
create and populate test table
create table epoch_t(etime numeric);
insert into epoch_t
select extract(epoch from generate_series(now(),now() - interval '6 hours',interval '-10 minutes'));
To divide up time into period buckets:
select generate_series(to_char(now(),'yyyy-mm-dd hh24:00:00')::timestamptz,
to_char(now(),'yyyy-mm-dd hh24:00:00')::timestamptz - interval '4 hours',
interval '-1 hour');
Convert epoch time to postgres timestamp:
select timestamptz 'epoch' + etime * '1 second'::interval from epoch_t;
then truncate to hour :
select to_char(timestamptz 'epoch' + etime * '1 second'::interval,
'yyyy-mm-dd hh24:00:00')::timestamptz from epoch_t
To provide summary information by hour :
select to_char(timestamptz 'epoch' + etime * '1 second'::interval,
'yyyy-mm-dd hh24:00:00')::timestamptz,
count(*)
from epoch_t
group by 1
order by 1 desc;
If you might have gaps in the data but need to report zero results use a generate_series to create period buckets and left join to data table.
In this case I create sample hour buckets back prior to the data population above - 9 hours instead of 6 and join on the conversion of epoch time to timestamp truncated to hour.
select per.sample_hour,
sum(case etime is null when true then 0 else 1 end) as etcount
from (select generate_series(to_char(now(),
'yyyy-mm-dd hh24:00:00')::timestamptz,
to_char(now(),'yyyy-mm-dd hh24:00:00')::timestamptz - interval '9 hours',
interval '-1 hour') as sample_hour) as per
left join epoch_t on to_char(timestamptz 'epoch' + etime * '1 second'::interval,
'yyyy-mm-dd hh24:00:00')::timestamptz = per.sample_hour
group by per.sample_hour
order by per.sample_hour desc;