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

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.

Related

PostgreSQL: Date Calendar Days Interval Scenario

I would like to print this table (displaying only 4 rows for brevity):
Dates
Period
01-MAR-2022
61
02-MAR-2022
61
03-MAR-2022
61
30-APR-2022
61
So far I have:
SELECT CAST(TRUNC(date_trunc('month',CURRENT_DATE) + interval '-2 month') AS DATE) + (n || 'day')::INTERVAL AS Dates
, date_trunc('month',CURRENT_DATE) + interval '-2 month' + INTERVAL '2 month' - date_trunc('month',CURRENT_DATE) + interval '-2 month' AS Period
FROM generate_series(0,61) n
Please help with a better way of generating the period and also replacing the hard-coded 61 in generate_series(0,61).
Thanks!
What are you actually trying to accomplish, it is not clear nor specified. BTW your query is invalid. It appears you looking to list each data from first date of 2 months prior to the last date of 1 month prior and the total number of days in that range. The following would give the first date, and using date subtraction gives the number of days.
with full_range( first_dt, num_days) as
( select date_trunc ('month', (current_date - interval '2 months'))::date
, date_trunc ('month', (current_date - interval '1 day'))::date -
date_trunc ('month', (current_date - interval '2 months'))::date
)
select *
from full_range;
With that in hand you can use the num_days with generate series with the expression
select generate_series(0, num_days-1) from full_range
Finally combine the above arriving at: (see demo)
with full_range( first_dt, num_days) as
( select date_trunc ('month', (current_date - interval '2 months'))::date
, date_trunc ('month', (current_date - interval '1 day'))::date -
date_trunc ('month', (current_date - interval '2 months'))::date
)
select (first_dt + n*interval '1 day')::date, num_days
from full_range
cross join (select generate_series(0, num_days-1) from full_range) gn(n);

Grouping data by quarter intervals (or any time interval) with a defined starting basis in postgresql

Let's say I have a table orders with columns amount and order_date.
I want to be able to group this data by quarters and aggregate the amount, the catch however is that the quarters do not start on January 1st but on any given arbitrary date, say July 12th. These quarters are also split in 13 week intervals. From what I see using something like date_trunc such as:
SELECT SUM(orders.amount), DATE_TRUNC('quarter', orders.order_date) AS interval FROM orders WHERE orders.order_date BETWEEN [date_start] AND [date_end] GROUP BY interval
is out of the question as this forces quarters to start on Jan 1st and it has 'hardcoded' quarter starting dates (Apr 1st, Jul 1st, etc).
I have tried using something like:
SELECT SUM(orders.amount),
to_timestamp(floor((extract('epoch' from orders.order_date / 7862400 )) * 7862400 ) AT TIME ZONE 'UTC' AS interval
FROM orders
WHERE orders.order_date BETWEEN [date_start] AND [date_end]
GROUP BY interval
(where 7862400 is the time interval that I want)
But with this method I cannot figure out how to set the offset for the initial grouping date, in my example I would like it to start from July 12th of each year (then count 13 weeks and start the next quarter, and so on). Hope I was clear and I would appreciate any help!
You can use generate_series() to create the first day of each quarter, join it and group by it.
SELECT quarters.first_day,
quarters.first_day + '13 weeks'::interval last_day,
sum(orders.amount) amount
FROM orders
LEFT JOIN generate_series('2019-07-12'::timestamp,
'2020-07-10'::timestamp,
'13 weeks'::interval) quarters (first_day)
ON quarters.first_day <= orders.order_date
AND quarters.first_day + '13 weeks'::interval > orders.order_date
WHERE orders.order_date BETWEEN [date_start]
AND [date_end]
GROUP BY quarters.first_day,
quarters.first_day + '13 weeks'::interval;
You just need to make sure, that the boundary days you give the generate_series() cover the whole period you want to query, so that depends on your [date_start] and [date_end].
You can generate your own 'quarterly calendar' and use that in place of the Postgers 'quarter' date extraction.
create or replace function quarterly_calendar(annual_date text default extract('YEAR' from current_date)::text)
returns table( quarter integer
, quarter_start_date date
, quarter_end_date date
)
language sql immutable strict leakproof
as $$
with RECURSIVE quarters as
(select 1 qtr, qdt::date q_start_dt, (qdt + interval '90 day' )::date q_end_dt, (qdt+interval '1 year' - interval '1 day')::date last_dt
from ( select date_trunc('year',current_date) + interval '6 month 11 day' qdt) q
union all
select qtr+1, (q_end_dt + interval '1 day')::date, least ((q_end_dt + interval '91 day')::date,last_dt), last_dt
from quarters
where qtr+1 <=5
)
select qtr, q_start_dt, q_end_dt
from quarters;
$$;
-- test
select * from quarterly_calender();
It does actually create 5 quarters. But that is because a year is not a multiple of 13 weeks (or 91 days or 7862400 seconds). In your given year from 12-July-2019 through 11-July-2020 is 2 days (366 days total) over 4 times that interval. You'll have to decide how to handle that 5th quarter. It occurs every year, having either 1 or 2 days. Hope this helps .

How to calculate end of the month in Postgres?

How to calculate end of the month in Postgres? I have table with column date datatype. I want to calculate end of the month of every date. For Eg. In the table there values like "2015-07-10 17:52:51","2015-05-30 11:30:19" then end of the month should be like 31 July 2015,31 May 2015.
Please guide me in this.
How about truncating to the beginning of this month, jumping forward one month, then back one day?
=# select (date_trunc('month', now()) + interval '1 month - 1 day')::date;
date
------------
2015-07-31
(1 row)
Change now() to your date variable, which must be a timestamp, per the docs. You can then manipulate this output (with strftime, etc.) to any format you need.
Source
SELECT TO_CHAR(
DATE_TRUNC('month', CURRENT_DATE)
+ INTERVAL '1 month'
- INTERVAL '1 day',
'YYYY-MM-DD HH-MM-SS'
) endOfTheMonth
Hi I tried like this and it worked
Date(to_char(date_trunc('month'::text, msm013.msa011) + '1 mon - 1 day '::interval , 'DD-MON-YYYY') )
Thanks a lot!!

postgres '1 year' equals '360 days'?

Am wondering if anyone else has encountered this or knows information about it.
Today is November 3, 2014 and if i check whether or not November 5, 2013 is within the last year i get different answers depending on how i check: 1 year versus 365 days
select now() - '20131105' as diff,
case when now() - '20131105' <= '1 year' then 'within year' else 'not within year' end as yr_check,
case when now() - '20131105' <= '365 days' then 'within 365 days' else 'not within 365 days' end as day_check
2014-11-03 16:27:38.39669-06; 363 days 16:27:38.39669; not within year; within 365 days
Looks like when querying against November 9 tho, it's ok
select now() as right_now, now() - '20131109' as diff,
case when now() - '20131109' <= '1 year' then 'within year' else 'not within year' end as yr_check,
case when now() - '20131109' <= '365 days' then 'within 365 days' else 'not within 365 days' end as day_check
2014-11-03 16:31:12.464469-06; 359 days 16:31:12.464469; within year; within 365 days
anyone have an idea about this? or is there something about date arithmetic that's funny?
postgres version is 9.2.4
or is there something about date arithmetic that's funny?
It's funny alright, but not in the way that makes you laugh.
Twelve months has to equal a year doesn't it?
=> SELECT '12 months'::interval = '1 year'::interval;
?column?
----------
t
Good. Makes sense. Hmm - wonder how long a month is.
=> SELECT '30 days'::interval = '1 month'::interval;
?column?
----------
t
Fair enough. Suppose they had to pick something.
Hmm - but that means...
=> SELECT '360 days'::interval = '12 months'::interval;
?column?
----------
t
Which seems to imply...
=> SELECT '360 days'::interval = '1 year'::interval;
?column?
----------
t
That can't be right! What they need to do is have a month equal to 30.41666 days. No hang on, what about leap years? Hmm - does this affect weeks? AARGH!
Basically, you can't convert sensibly between time units. There aren't 60 seconds in a minute, or 24 hours in a day, 52 weeks in a year or even 365 days. Unfortunately, humans (particularly customer-shaped humans) like converting between time units so we end up with a mess like this.
PostgreSQL's system is no more loony than any other and in fact is better than most.
I'm not sure what is real problem with this check, but it works other way around:
select now() - interval '1 year' <= date '2013-11-05'
I'm no expert in Postgres, but it can be something with type comparisons, because:
select pg_typeof(now() - date '2013-11-05'),
pg_typeof(now() - interval '1 year')
yields result:
interval, timestamp with time zone
so your example compares interval with interval, but for different scales - days vs year, and my solution compares timestamp with date, which seems to work
UPDATE:
You can check that interval '1 year' when not attached to year (not added to date or timestamp) equals to 360 days:
select interval '1 year' <= interval '359 days',
interval '1 year' <= interval '360 days'
which yields:
f, t
From my understanding you can't just compare random year interval when you don't know year it is attached - always compare dates, and just use interval to create new date object.
select now() - interval '1 year' <= now() - interval '365 days'
t
From www.postgresql.org/docs/current/static/datatype-datetime.html:
Internally interval values are stored as months, days, and seconds. This is done because the number of days in a month varies, and a day can have 23 or 25 hours if a daylight savings time adjustment is involved. The months and days fields are integers while the seconds field can store fractions. Because intervals are usually created from constant strings or timestamp subtraction, this storage method works well in most cases. Functions justify_days and justify_hours are available for adjusting days and hours that overflow their normal ranges.
Because you compare two intervals, PostgreSQL internally normalizes values (like justify_interval()), before comparing:
SELECT INTERVAL '31 days' > INTERVAL '1 mon' -- yields 't'
But, if you apply interval substraction/addition, varying day & month length taken into consideration:
SELECT (timestamptz '2014-11-03 00:00:00 America/New_York' - INTERVAL '1 day') AT TIME ZONE 'America/New_York',
timestamptz '2014-11-03 00:00:00 America/New_York' - timestamptz '2014-11-02 00:00:00 America/New_York' <= interval '1 day';
-- | timestamp | boolean |
-- +---------------------+---------+
-- | 2014-11-02 01:00:00 | f |
So, if you need to test, whether a timestamp/date is within a range, you should manipulate timestampts/dates (or use timestamp/date ranges) & compare those values with <, > or BETWEEN.
SELECT timestamp '2014-11-03 00:00:00' - timestamp '2014-10-03 00:00:00' <= interval '1 mon',
timestamp '2014-11-03 00:00:00' - interval '1 mon' <= timestamp '2014-10-03 00:00:00';
-- | boolean | boolean |
-- +---------+---------+
-- | f | t |

get last three month records from table

How to get last 3 months records from the table.
SELECT *
from table
where month > CURRENT_DATE-120
and month < CURRENT_DATE
order by month;
I have used the above query is it correct? shall I use this for get last 3 month record from the table.
You can use built-in INTERVAL instruction
Check how this works:
SELECT CURRENT_DATE - INTERVAL '3 months'
and you can rewrite your SQL to:
SELECT * from table where date > CURRENT_DATE - INTERVAL '3 months'
(not checked but this should give you an idea how to use INTERVAL instruction)
Try that:
SELECT *
FROM table
WHERE month BETWEEN EXTRACT(MONTH FROM NOW() - INTERVAL '3 months')
AND EXTRACT(MONTH FROM NOW())
ORDER BY month
;
This filters the last 3 calendar months
SELECT * from table where date >= to_char(CURRENT_DATE - INTERVAL '3 months', 'YYYY-MM-01')::date
select date::date
from generate_series((current_date - INTERVAL '1 Month')::date, (current_date - INTERVAL '1 DAY')::date,'1
day'::interval) date
WHERE date >= date_trunc('month', current_date - interval '3' month)
and date < date_trunc('month', current_date)
This will give last three months date list, excluding current months date. Example if current month is November. This list will give use all dates of August, Septemeber and October.