How to get year interval between two date in postgresql? - sql

i want the year intervals like this
fromdate = 15-09-2019, todate=21-09-2021
15-09-2019 - 31-12-2019 //for the year 2019
01-01-2020 - 31-12-2020 //for the year 2020
01-01-2021 - 21-12-2021 //for the year 2021
so that i can able to get data from those intervals
In this table i have values only for 2021 but not for 2019 and 2020
If any there is no data in the range of 2019 and 2020.It should prdouce 0 for the parameter Traincount and tonnage.

Simply subtract them:
SELECT ('2015-01-12'::date - '2015-01-01'::date) AS days;
The result:
days
------
11

There is a brute force approach to getting a yearly summary, which is to break the data out by days and then aggregate:
select min(dte), max(dte)
from generate_series('2019-09-15'::date, '2021-09-21'::date, interval '1 day') gs(dte)
group by date_trunc('year', dte);
However, more efficient method would avoid aggregation:
select greatest(gs.dte, v.fromdate),
least(gs.dte + interval '1 year - 1 day', v.todate)
from (values ('2019-09-15'::date, '2021-09-21'::date)) v(fromdate, todate) cross join lateral
generate_series(date_trunc('year', v.fromdate), date_trunc('year', v.todate), interval '1 year') gs(dte);
Here is a db<>fiddle.

You can try
select extract('year' from now()) - extract('year' from now());

Related

postgresql show month start date and end date based on given dates

I need to pull out first date and last date of the month from the given from_date and to_date as input, For Example:-
I have my psql output table as the following:
Year
Term
Start Date
End Date
2022
Odd
01-02-2022
30-04-2022
2022
Even
01-07-2022
30-09-2022
I need the output as the following:-
Year
Term
Start Date
End Date
2022
Odd
01-02-2022
28-02-2022
2022
Odd
01-03-2022
31-03-2022
2022
Odd
01-04-2022
30-04-2022
2022
Even
01-07-2022
30-07-2022
2022
Even
01-08-2022
31-08-2022
2022
Even
01-09-2022
30-09-2022
I need the ouput in Postgresql, Pls help
Thanks
Your issue boils down to given a period with start and end dates, determine the first and last dates for each month in that period. In Postgres given a date you can determine the first (with date_trunc function) and last of the a month with the expressions:
-- for a given date
date_trunc('month', given_date) -- returns first day of month
date_trunc('month', given_date + interval '1 month' - interval '1 day') -- returns last day of month
Use the first expression above, with generate_series with dates, to create the first of each month in the period. The use the second expression to generate the end of each month. (see demo)
with range_dates (year, term, gsom) as
( select year
, term
, generate_series( date_trunc('month', od.start_date)::date
, date_trunc('month', od.end_date )::date
, interval '1 month'
)::date
from output_data od
)
select year
, term
, gsom start_date
, (gsom + interval '1 month' - interval '1 day')::date end_date
from range_dates
order by term desc, start_date;

Does date_trunc on current_date automatically pick date of current year in postgres?

SELECT *
FROM public.business_calendar;
Above query gives me all data including year 2020 and 2021.
SELECT *
FROM public.business_calendar
WHERE date_trunc('month', business_date) = date_trunc('month', CURRENT_DATE) order by business_date
Above query gives me data of Jan 2021 and not Jan 2020. Does date_trunc automatically work on current year when used with CURRENT_DATE?
Does date_trunc automatically work on current year when used with CURRENT_DATE?
Yes, date_trunc('month', CURRENT_DATE) will truncate the current date.
You may be misunderstanding what date_trunc does. date_trunc('month', CURRENT_DATE) does not return the month, it returns a complete timestamp at the start of the month. On January 17th, 2021 it will return 2021-01-01 00:00:00.
If you want just the month use extract.
test=> select current_date;
current_date
--------------
2021-01-17
(1 row)
test=> select date_trunc('month', current_date);
date_trunc
------------------------
2021-01-01 00:00:00-08
(1 row)
test=> select extract(month from current_date);
date_part
-----------
1
(1 row)
If you want to match dates in the current month ignoring the year...
select *
from public.business_calendar
where extract(month from business_date) = extract(month from current_date)
order by business_date

How to get number of days in month based on date?

Is there a way to use extract from date in format YYYY-MM-DD how many days were in this month?
example:
for 2016-02-05 it will give 29 (Feb 2016 has 29 days)
for 2016-03-12 it will give 31
for 2015-02-05 it will give 28 (Feb 2015 had 28 days)
I'm using PostgreSQL
EDIT:
LAST_DAY function in postgres is not what i'm looking for. it returns DATE while I expect an Integer
One way to achieve this would be to subtract the beginning of the following month from the beginning of the current month:
db=> SELECT DATE_TRUNC('MONTH', '2016-02-05'::DATE + INTERVAL '1 MONTH') -
DATE_TRUNC('MONTH', '2016-02-05'::DATE);
?column?
----------
29 days
(1 row)
Just needed this today and seems that I came up with pretty much the same as Mureinik, just that I needed it numeric. (PostgreSQL couldn't convert from interval to number directly)
previous month:
select CAST(to_char(date_trunc('month', current_date) - (date_trunc('month', current_date) - interval '1 month'),'dd') as integer)
current month:
select CAST(to_char(date_trunc('month', current_date) + interval '1 month' - date_trunc('month', current_date), 'dd') as integer)
You can try next:
SELECT
DATE_PART('days',
DATE_TRUNC('month', TO_DATE('2016-02-05', 'YYYY-MM-DD'))
+ '1 MONTH'::INTERVAL
- DATE_TRUNC('month', TO_DATE('2016-02-05', 'YYYY-MM-DD'))
);
Note: there date is used twice. And used convert function TO_DATE

Generate series of week intervals for given month

In a Postgres 9.1 database, I am trying to generate a series of weeks for a given month but with some constraints. I need all weeks to start on Monday and get cut when they start or end in another month.
Example:
For February, 2013 I want to generate a series like this:
start
------------------------
2013-02-01 00:00:00+00
2013-02-04 00:00:00+00
2013-02-11 00:00:00+00
2013-02-18 00:00:00+00
2013-02-25 00:00:00+00
The query that I have now looks like this:
SELECT GREATEST(date_trunc('week', dates.d),
date_trunc('month',dates.d)) as start
FROM generate_series(to_timestamp(1359676800),to_timestamp(1362095999), '1 week') as dates(d)
This query gets me the first 4 weeks but it's missing the week from the 25th. Is it possible to get the last week?
SELECT generate_series(date_trunc('week', date '2013-02-01' + interval '6 days')
, date_trunc('week', date '2013-02-01' + interval '1 month - 1 day')
, interval '1 week')::date AS day
UNION SELECT date '2013-02-01'
ORDER BY 1;
This variant does not need a subselect, GREATEST or GROUP BY and only generates the required rows. Simpler, faster. It's cheaper to UNION one row.
Add 6 days to the first day of the month before date_trunc('week', ...) to compute the first Monday of the month.
Add 1 month and subtract 1 day before date_trunc('week', ...) to get the last Monday of the month.
This can conveniently be stuffed into a single interval expression: '1 month - 1 day'
UNION (not UNION ALL) the first day of the month to add it unless it's already included as Monday.
Note that date + interval results in timestamp, which is the optimum here. Detailed explanation:
Generating time series between two dates in PostgreSQL
Automation
You can provide the start of the date series in a CTE:
WITH t(d) AS (SELECT date '2013-02-01') -- enter 1st of month once
SELECT generate_series(date_trunc('week', d + interval '6 days')
, date_trunc('week', d + interval '1 month - 1 day')
, interval '1 week')::date AS day
FROM t
UNION SELECT d FROM t
ORDER BY 1;
Or wrap it into a simple SQL function for convenience with repeated calls:
CREATE OR REPLACE FUNCTION f_week_starts_this_month(date)
RETURNS SETOF date AS
$func$
SELECT generate_series(date_trunc('week', $1 + interval '6 days')
, date_trunc('week', $1 + interval '1 month - 1 day')
, interval '1 week')::date AS day
UNION
SELECT $1
ORDER BY 1
$func$ LANGUAGE sql IMMUTABLE;
Call:
SELECT * FROM f_week_starts_this_month('2013-02-01');
You would pass the date for the first day of the month, but it works for any date. You the first day and all Mondays for the following month.
select
greatest(date_trunc('week', dates.d), date_trunc('month',dates.d)) as start
from generate_series('2013-02-01'::date, '2013-02-28', '1 day') as dates(d)
group by 1
order by 1

Postgresql generate_series of months

I'm trying to generate a series in PostgreSQL with the generate_series function. I need a series of months starting from Jan 2008 until current month + 12 (a year out). I'm using and restricted to PostgreSQL 8.3.14 (so I don't have the timestamp series options in 8.4).
I know how to get a series of days like:
select generate_series(0,365) + date '2008-01-01'
But I am not sure how to do months.
select DATE '2008-01-01' + (interval '1' month * generate_series(0,11))
Edit
If you need to calculate the number dynamically, the following could help:
select DATE '2008-01-01' + (interval '1' month * generate_series(0,month_count::int))
from (
select extract(year from diff) * 12 + extract(month from diff) + 12 as month_count
from (
select age(current_timestamp, TIMESTAMP '2008-01-01 00:00:00') as diff
) td
) t
This calculates the number of months since 2008-01-01 and then adds 12 on top of it.
But I agree with Scott: you should put this into a set returning function, so that you can do something like select * from calc_months(DATE '2008-01-01')
You can interval generate_series like this:
SELECT date '2014-02-01' + interval '1' month * s.a AS date
FROM generate_series(0,3,1) AS s(a);
Which would result in:
date
---------------------
2014-02-01 00:00:00
2014-03-01 00:00:00
2014-04-01 00:00:00
2014-05-01 00:00:00
(4 rows)
You can also join in other tables this way:
SELECT date '2014-02-01' + interval '1' month * s.a AS date, t.date, t.id
FROM generate_series(0,3,1) AS s(a)
LEFT JOIN <other table> t ON t.date=date '2014-02-01' + interval '1' month * s.a;
You can interval generate_series like this:
SELECT TO_CHAR(months, 'YYYY-MM') AS "dateMonth"
FROM generate_series(
'2008-01-01' :: DATE,
'2008-06-01' :: DATE ,
'1 month'
) AS months
Which would result in:
dateMonth
-----------
2008-01
2008-02
2008-03
2008-04
2008-05
2008-06
(6 rows)
Well, if you only need months, you could do:
select extract(month from days)
from(
select generate_series(0,365) + date'2008-01-01' as days
)dates
group by 1
order by 1;
and just parse that into a date string...
But since you know you'll end up with months 1,2,..,12, why not just go with select generate_series(1,12);?
In the generated_series() you can define the step, which is one month in your case. So, dynamically you can define the starting date (i.e. 2008-01-01), the ending date (i.e. 2008-01-01 + 12 months) and the step (i.e. 1 month).
SELECT generate_series('2008-01-01', '2008-01-01'::date + interval '12 month', '1 month')::date AS generated_dates
and you get
1/1/2008
2/1/2008
3/1/2008
4/1/2008
5/1/2008
6/1/2008
7/1/2008
8/1/2008
9/1/2008
10/1/2008
11/1/2008
12/1/2008
1/1/2009