Dynamic (Column Based) Interval - postgresql

How do I add a dynamic (column based) number of days to NOW?
SELECT NOW() + INTERVAL a.number_of_days "DAYS" AS "The Future Date"
FROM a;
Where a.number_of_days is an integer?

I usually multiply the number by interval '1 day' or similar, e.g.:
select now() + interval '1 day' * a.number_of_days from a;

I know this is a year old, but if you need to use a column to specify the actual interval (e.g. 'days', 'months', then it is worth knowing that you can also CAST your string to an Interval, giving:
SELECT now()+ CAST(the_duration||' '||the_interval AS Interval)
So the the original question would become:
SELECT now() + CAST(a.number_of_days||" DAYS" AS Interval) as "The Future Date" FROM a;

I prefer this way. I think its pretty easy and clean.
In Postgres you need interval to use + operator with timestamp
select (3||' seconds')::interval;
select now()+ (10||' seconds')::interval,now();
where you can use seconds, minutes, days, months...
and you can replace the numbers to your column.
select now()+ (column_name||' seconds')::interval,now()
from your_table;

Use make_interval()
SELECT NOW() + make_interval(days => a.number_of_days) AS "The Future Date"
FROM a;
But in general it might be a better idea to use a column defined as interval, then you can use any unit you want when you store a value in there.

To creating intervals those based on column values, I recommend to add two columns in your table. For example, column "period_value"::INT4 and column "period_name"::VARCHAR.
Column "period_name" can store the following values:
microsecond
milliseconds
second
minute
hour
day
week
month
quarter
year
decade
century
millennium
+--------------+-------------+
| period_value | period_name |
+--------------+-------------+
| 2 | minute |
+--------------+-------------+
Now you can write:
SELECT NOW() - (period_value::TEXT || ' ' || period_name::TEXT)::INTERVAL FROM table;

If we have field with interval string value such as '41 years 11 mons 4 days' and want to convert it to date of birth use this query :
UPDATE "february14" set dob = date '2014/02/01' - (patient_age::INTERVAL)
dob is date field to convert '41 years 11 mons 4 days' to '1972/10/14' for example
patient_age is varchar field that have string like '41 years 11 mons 4 days'
And this is query to convert age back to date of birth
SELECT now() - INTERVAL '41 years 10 mons 10 days';

Updating based on a column ID was a useful way to create some randomised test data for me.
update study_histories set last_seen_at = now() - interval '3 minutes' * id;

Related

Postgresql timestamp difference greater than 1 hour

Hi I have a entrytime and exittime timestamp in my database, how can I query it to display only ones where the person exited more than an hour later;
Select * from store where EXTRACT(EPOCH FROM (exittime - entrytime))/3600 >60
That's what I have so far but it won't work, any help would be appreciated.
Just subtract the values and compare it with an interval
Select *
from store
where exittime - entrytime > interval '1 hour';
This assumes that both columns are defined as timestamptz or timestamp

postgresql query for hour minutes and seconds

Hi I am having a Postgresql query like below to calculate DateTime difference for {1} and {2} in minutes.
CAST(ROUND(EXTRACT(EPOCH from (({2}::timestamp) - ({1}::timestamp)))/60) AS INT)
I want to calculate the difference in hours, minutes and seconds displayed like:
3 hrs 31 minutes 42 secs
What manipulation do I need for displaying like above?
SELECT to_char((col1 - col0), 'HH24 hrs MI "minutes" SS "seconds"') FROM T1;
Here is a sqlfiddle : link
The to_char function takes an interval (an interval is the time span between two timestamps, and subtracting timestamps gives you an interval). It then takes a formatting, and you can apply pretty much what you want.
Formatting functions in PostgreSQL
Try use this sql:
SELECT to_char(column2 - column1, 'DD" days "HH24" hours "MI" minutes "SS" seconds"');
The subtraction of two timestamp or timestamptz values produces an interval. (While subtracting two date values produces an integer!)
Details about date/time types in the manual.
The default text representation of an interval may be sufficient:
SELECT timestamp '2017-1-6 12:34:56' - timestamp '2017-1-1 0:0';
Result is an interval, displayed as:
5 days 12:34:56
If you need the format in the question, precisely, you need to specify how to deal with intervals >= 24 hours. Add 'days'? Or just increase hours accordingly?
#Nobody provided how to use to_char(). But add days one way or the other:
SELECT to_char(ts_col2 - ts_col1, 'DD" days "HH24" hours "MI" minutes "SS" seconds"');
Result:
05 days 12 hours 34 minutes 56 seconds
'days' covers the rest. There are no greater time units in the result by default.
Simple
SELECT
EXTRACT(year FROM LOCALTIMESTAMP(0) - yourFieldTime)||' year '||
EXTRACT(month FROM LOCALTIMESTAMP(0) - yourFieldTime)||' month '||
EXTRACT(day FROM LOCALTIMESTAMP(0) - yourFieldTime)||' day '||
EXTRACT(hour FROM LOCALTIMESTAMP(0) - yourFieldTime)||' hour '||
EXTRACT(minute FROM LOCALTIMESTAMP(0) - yourFieldTime)||' minute '||
EXTRACT(second FROM LOCALTIMESTAMP(0) - yourFieldTime)||' second '
AS full_time_as_you_wish FROM your_table;
Result
full_time_as_you_wish
---------------------------------
0 year 0 month 0 day 0 hour 0 minute 0 second

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!!

Creating a date series in postgresql 8.3

I am trying to create a series of dates from a fixed date in past to current date, in month increments. I know this is possible in 8.4 with a new feature but i am stuck with 8.3 for now.
I feel I am going down a rabbit hole here as I have this sql to get me monthly increments
SELECT date('2008-01-01') + (to_char(a,'99')||' month')::interval as date FROM generate_series(0,20) as a;
I am then trying to extract months and years from the interval of current date - fixed date
SELECT extract( month from interval (age(current_date, date('2008-01-01'))) );
but im beginning to think this is a silly way to get the desired date series.
Could work like this:
SELECT ('2008-01-01 0:0'::timestamp
+ interval '1 month' * generate_series(0, months))::date
FROM (
SELECT (extract(year from intv) * 12
+ extract(month from intv))::int4 AS months
FROM (SELECT age(now(), '2008-01-01 0:0'::timestamp) as intv) x
) y
In case someone would need e.g. 3 hour interval inside given date range:
SELECT ('2013-01-01 0:0'::timestamp
+ interval '1 hour' * generate_series(0, ('2013-02-01'::date - '2013-01-01'::date)*24, 3))::timestamp;

Postgres birthdays selection

I work with a Postgres database. This DB has a table with users, who have a birthdate (date field). Now I want to get all users who have their birthday in the upcoming week....
My first attempt: SELECT id FROM public.users WHERE id IN (lange reeks) AND birthdate > NOW() AND birthdate < NOW() + interval '1 week'
But this does not result, obviously because off the year. How can I work around this problem?
And does anyone know what happen to PG would go with the cases at 29-02 birthday?
We can use a postgres function to do this in a really nice way.
Assuming we have a table people, with a date of birth in the column dob, which is a date, we can create a function that will allow us to index this column ignoring the year. (Thanks to Zoltán Böszörményi):
CREATE OR REPLACE FUNCTION indexable_month_day(date) RETURNS TEXT as $BODY$
SELECT to_char($1, 'MM-DD');
$BODY$ language 'sql' IMMUTABLE STRICT;
CREATE INDEX person_birthday_idx ON people (indexable_month_day(dob));
Now, we need to query against the table, and the index. For instance, to get everyone who has a birthday in April of any year:
SELECT * FROM people
WHERE
indexable_month_day(dob) >= '04-01'
AND
indexable_month_day(dob) < '05-01';
There is one gotcha: if our start/finish period crosses over a year boundary, we need to change the query:
SELECT * FROM people
WHERE
indexable_month_day(dob) >= '12-29'
OR
indexable_month_day(dob) < '01-04';
To make sure we match leap-day birthdays, we need to know if we will 'move' them a day forward or backwards. In my case, it was simpler to just match on both days, so my general query looks like:
SELECT * FROM people
WHERE
indexable_month_day(dob) > '%(start)%'
%(AND|OR)%
indexable_month_day(dob) < '%(finish)%';
I have a django queryset method that makes this all much simpler:
def birthday_between(self, start, finish):
"""Return the members of this queryset whose birthdays
lie on or between start and finish."""
start = start - datetime.timedelta(1)
finish = finish + datetime.timedelta(1)
return self.extra(where=["indexable_month_day(dob) < '%(finish)s' %(andor)s indexable_month_day(dob) > %(start)s" % {
'start': start.strftime('%m-%d'),
'finish': finish.strftime('%m-%d'),
'andor': 'and if start.year == finish.year else 'or'
}]
def birthday_on(self, date):
return self.birthday_between(date, date)
Now, I can do things like:
Person.objects.birthday_on(datetime.date.today())
Matching leap-day birthdays only on the day before, or only the day after is also possible: you just need to change the SQL test to a `>=' or '<=', and not adjust the start/finish in the python function.
I'm not overly confident in this, but it seems to work in my testing. The key here is the OVERLAPS operator, and some date arithmetic.
I assume you have a table:
create temporary table birthdays (name varchar, bday date);
Then I put some stuff into it:
insert into birthdays (name, bday) values
('Aug 24', '1981-08-24'), ('Aug 04', '1982-08-04'), ('Oct 10', '1980-10-10');
This query will give me the people with birthdays in the next week:
select * from
(select *, bday + date_trunc('year', age(bday)) + interval '1 year' as anniversary from birthdays) bd
where
(current_date, current_date + interval '1 week') overlaps (anniversary, anniversary)
The date_trunc truncates the date at the year, so it should get you up to the current year. I wound up having to add one year. This suggests to me I have an off-by-one in there for some reason. Perhaps I just need to find a way to get dates to round up. In any case, there are other ways to do this calculation. age gives you the interval from the date or timestamp to today. I'm trying to add the years between the birthday and today to get a date in the current year.
The real key is using overlaps to find records whose dates overlap. I use the anniversary date twice to get a point-in-time.
Finally, to show the upcoming birthdays of the next 14 days I used this:
SELECT
-- 14 days before birthday of 2000
to_char( to_date(to_char(c.birthdate, '2000-MM-dd'), 'YYYY-MM-dd') - interval '14 days' , 'YYYY-MM-dd') as _14b_b2000,
-- birthday of 2000
to_date(to_char(c.birthdate, '2000-MM-dd'), 'YYYY-MM-dd') as date_b2000,
-- current date of 2000
to_date(to_char(current_date, '2000-MM-dd'), 'YYYY-MM-dd') as date_c2000,
-- 14 days after current date of 2000
to_char( to_date(to_char(current_date, '2000-MM-dd'), 'YYYY-MM-dd') + interval '14 days' , 'YYYY-MM-dd') as _14a_c2000,
-- 1 year after birthday of 2000
to_char( to_date(to_char(c.birthdate, '2000-MM-dd'), 'YYYY-MM-dd') + interval '1 year' , 'YYYY-MM-dd') as _1ya_b2000
FROM c
WHERE
-- the condition
-- current date of 2000 between 14 days before birthday of 2000 and birthday of 2000
to_date(to_char(current_date, '2000-MM-dd'), 'YYYY-MM-dd') between
to_date(to_char(c.birthdate, '2000-MM-dd'), 'YYYY-MM-dd') - interval '14 days' and
to_date(to_char(c.birthdate, '2000-MM-dd'), 'YYYY-MM-dd')
or
-- 1 year after birthday of 2000 between current date of 2000 and 14 days after current date of 2000
to_date(to_char(c.birthdate, '2000-MM-dd'), 'YYYY-MM-dd') + interval '1 year' between
to_date(to_char(current_date, '2000-MM-dd'), 'YYYY-MM-dd') and
to_date(to_char(current_date, '2000-MM-dd'), 'YYYY-MM-dd') + interval '14 days'
;
So:
To solve the leap-year issue, I set both birthdate and current date to 2000,
and handle intervals only from this initial correct dates.
To take care of the near end/beginning dates,
I compared first the 2000 current date to the 2000 birthday interval,
and in case current date is at the end of the year, and the birthday is at the beginning,
I compared the 2001 birthday to the 2000 current date interval.
Here's a query that gets the right result, most of the time.
SELECT
(EXTRACT(MONTH FROM DATE '1980-08-05'),
EXTRACT(DAY FROM DATE '1980-08-05'))
IN (
SELECT EXTRACT(MONTH FROM CURRENT_DATE + s.a) AS m,
EXTRACT(DAY FROM CURRENT_DATE + s.a) AS d
FROM GENERATE_SERIES(0, 6) AS s(a)
);
(it doesn't take care of leap years correctly; but you could use extract again to work the subselect in terms of a leap year instead of the current year.
EDIT: Got it working for all cases, and as a useful query rather than a scalar select. I'm using some extra subselects so that I don't have to type the same date or expression twice for month and day, and of course the actual data would be in a table instead of the values expression. You might adapt this differently. It might still stand to improve by making a more intelligent series for weeks containing leap days, since sometimes that interval will only contain 6 days (for non-leap years).
I'll try to explain this from the inside-out; First thing I do is normalize the target date (CURRENT_DATE usually, but explicit in this code) into a year that I know is a leap year, so that February 29th appears among dates. The next step is to generate a relation with all of the month-day pairs that are under consideration; Since there's no easy way to do an interval check in terms of month-day, it's all happening using generate_series,
From there it's a simple matter of extracting the month and day from the target relation (the people alias) and filtering just the rows that are in the subselect.
SELECT *
FROM
(select column1 as birthdate, column2 as name
from (values
(date '1982-08-05', 'Alice'),
(date '1976-02-29', 'Bob'),
(date '1980-06-10', 'Carol'),
(date '1992-06-13', 'David')
) as birthdays) as people
WHERE
((EXTRACT(MONTH FROM people.birthdate),
EXTRACT(DAY FROM people.birthdate)) IN (
SELECT EXTRACT(MONTH FROM thedate.theday + s.a) AS m,
EXTRACT(DAY FROM thedate.theday + s.a) AS d
FROM
(SELECT date (v.column1 -
(extract (YEAR FROM v.column1)-2000) * INTERVAL '1 year'
) as theday
FROM (VALUES (date '2011-06-09')) as v) as thedate,
GENERATE_SERIES(0, 6) AS s(a)
)
)
Operating on days, as I've done here, should work splendidly all the way up until a two month interval (if you wanted to look out that far), since december 31 + two months and change should include the leap day. On the other hand, it's almost certainly more useful to just work on whole months for such a query, in which case you don't really need anything more than extract(month from ....
First find out how old the person currently is using age(), then grab the year from that extract(year from age()). This is how old they are currently in years, so for their age at their next birthday add 1 to the year. Then their next birthday is found by adding an interval of this many years * interval '1 year' to their birthday. Done.
I've used a subselect here to add the next_birth_day column in to the complete table to make the select clause simpler. You can then play with the where conditions to suit your needs.
select *
from (
select *,
(extract(year from age(birth_date)) + 1) * interval '1 year' + birth_date "next_birth_day"
from public.users
) as users_with_upcoming_birth_days
where next_birth_day between now() and now() + '7 days'
This is based on Daniel Lyons's anniversary idea, by calculating the interval between the next birthday and today, with just +/- date arithmetic:
SELECT
today,
birthday,
CASE
WHEN this_year_anniversary >= today
THEN this_year_anniversary
ELSE this_year_anniversary + '1 year'::interval
END - today < '1 week'::interval AS is_upcoming
FROM
(
SELECT
today,
birthday,
birthday + years AS this_year_anniversary
FROM
(
SELECT
today,
birthday,
((
extract(year FROM today) - extract(year from birthday)
) || ' years')::interval AS years
FROM
(VALUES ('2011-02-28'::date)) AS t1 (today),
(VALUES
('1975-02-28'::date),
('1975-03-06'::date),
('1976-02-28'::date),
('1976-02-29'::date),
('1976-03-06'::date)
) AS t2 (birthday)
) AS t
) AS t;
In case you want it to work with leap years:
create or replace function birthdate(date)
  returns date
as $$
  select (date_trunc('year', now()::date)
         + age($1, 'epoch'::date)
         - (extract(year from age($1, 'epoch'::date)) || ' years')::interval
         )::date;
$$ language sql stable strict;
Then:
where birthdate(birthdate) between current_date
and current_date + interval '1 week'
See also:
Getting all entries who's Birthday is today in PostgreSQL
Exemple: birthdate between: jan 20 and feb 10
SELECT * FROM users WHERE TO_CHAR(birthdate, '1800-MM-DD') BETWEEN '1800-01-20' AND '1800-02-10'
Why 1800?
No matter may be any year;
In my registration form, I can inform the date of birth (with years) or just the birthday (without year), in which case I saved as 1800 to make it easier to work with the date
Here's my take, which works with leap years too:
CREATE OR REPLACE FUNCTION days_until_birthday(
p_date date
) RETURNS integer AS $$
DECLARE
v_now date;
v_days integer;
v_date_upcoming date;
v_years integer;
BEGIN
v_now = now()::date;
IF (p_date IS NULL OR p_date > v_now) THEN
RETURN NULL;
END IF;
v_years = date_part('year', v_now) - date_part('year', p_date);
v_date_upcoming = p_date + v_years * interval '1 year';
IF (v_date_upcoming < v_now) THEN
v_date_upcoming = v_date_upcoming + interval '1 year';
END IF;
v_days = v_date_upcoming - v_now;
RETURN v_days;
END
$$ LANGUAGE plpgsql IMMUTABLE;
I know this post is old, but I had the same issue and came up with this simple and elegant solution:
It is pretty easy with age() and accounts for lap years... for the people who had their birthdays in the last 20 days:
SELECT * FROM c
WHERE date_trunc('year', age(birthdate)) != date_trunc('year', age(birthdate + interval '20 days'))
I have simply created this year date from original birth date.
( DATE_PART('month', birth_date) || '/' || DATE_PART('day', birth_date) || '/' || DATE_PART('year', now()))::date between :start_date and :end_date
I hope this help.