Creating flexible date ranges in PostgreSQL - postgresql

I want to make my query more flexible with dates. I am thinking of defining variables with reference to current date. currently I am coding the exact date for the last quarter, the quarter before and the quarter one year ago.
That is the query for Q1:
select '2020_Q1' as time_frame, id, status, date, agent, country, sale
from sales
where date >= '2020-01-01' and date < '2020-03-31'
I do the same for Q4 and Q1 (2019) and union in the end. Today is April 27, let's say it is 4 months from now and it is August 27th. Now, I would want to look at Q2,Q1 and Q2(2019). I believe, I would need to work with current_date, but let me know if you think there is a more efficient way.

You can use date_trunc() to get the start of the "current quarter":
select ...
from sales
where date => date_trunc('quarter', current_date)
and date < date_trunc('quarter', current_date) + interval '3 months'

Related

How to find the 1st and 3rd Sunday/Monday of a current month in postgresql

Can anyone help me to find the specific week day of the month in postgresql... like 1st and 3rd week sunday/monday or 2nd and 4th week wednesday
First off contrary to initial expectations working with dates is complex, sometime extremely so. The combination of week numbers and days the week fall into the latter category.
The problem stems from the 2 ISO definitions:
All weeks start on Monday and are 7 days long.
The first week of the year is the week containing 4-Jan.
This dooms any effort (at least any reasonable simple onc) to failure. While an admirable effort I'll use #Abelisto suggestion as a sample. See Fiddle. I've changed that just enough to use multiple parameters, while for most months it's correct but look at 30,31-Jan-2019 and Jan-2021.
The problem with the first being while the ISO week is perfectly consistent the calendar is not. This results that the first week of a given month be the same as the last week of the previous month, and the reverse.
While this can usually be worked around by itself not so when combined with the other. As a result of each being 7 days long and the 1st week of the year containing 4-Jan gives rise to the larger problem. The last few days of Dec maybe in the 1st week of the next year. Also the first days of Jan can be in the 52( or 53) week of the prior year (see 2nd query in fiddle). Is there a solution? I'm sure there is somewhere out there. I just don't have it. At least with the Extract function.
So how about this specific issue: Well basically it comes down to getting the last day of the previous month, then finding the next DOW (Sunday or Monday) as needed. Now coming from a Oracle back ground I'd just use the NEXT_DAY function which would do just that for me. Unfortunately Postgres does not provide that useful function. But you can roll your own. Below I provide a a couple functions I wrote to do this functionality in Postgres. It consists of 2 Postgres SQL functions:
- utl_dates_first_dow_of_month(). It takes 2 parameters, the target Day-Of-Week (DOW) as the first 3 characters of the day name (case insensitive) and a date in the desired month. It returns the DATE which is the first occurrence of the requested DOW.
- utl_dates_next_dow(). It takes the same 2 parameters and returns the next calendar date of the specified DOW from the from the specified date. If the date specified fall on the requested DOW the routine DOES NOT return the specified date. Function is actually used by the first.
Fortunately the routines are shorter than the description.
create or replace function utl_dates_next_dow(dow_in text, date_in date)
returns date
language sql
immutable strict
as $$
-- Given a DOW and a date return the calendar date for the next occurrence of DOW
with dy as (select string_to_array('mon,tue,wed,thu,fri,sat,sun', ',') dl)
, dn as (select array_position(dl, (substring(to_char(date_in, 'day'),1,3))) fn
, array_position(dl, lower(substring(dow_in,1,3))) dn
from dy
)
select case when dn <= fn
then (date_in + (dn+7-fn) * interval '1 day')::date
else (date_in + (dn-fn) * interval '1 day')::date
end
from dn;
$$;
create or replace function utl_dates_first_dow_of_month(dow_in text, date_in date)
returns date
language sql
immutable strict
as $$
-- Given a DOW and a Date return the calendar date of the first specified dow in which the specified date falls.
select utl_dates_next_dow(dow_in, (date_trunc('month', date_in) - interval '1 day')::date);
$$;
Now with that out out of the way on the the issue at hand. As Abelisto, and others, indicate the request is ambiguous. There is no such thing as 1st or 3rd Sunday/Monday. Do you want the 1st and 3rd Sunday and the 1st and 3rd Monday of the month? Do you want
Do you want the 1st and 3rd Sunday of the month and the Monday following each respectively. Do you want the Sunday and Monday for the 1st and 3rd week on the month (If so Monday would always be the earlier date, see definition 1 above)? Please try to be more specific with your questions. And include test data - as text no images - and the expected results from that data. The solutions are however just slight modifications of each other. (No solution for the 3rd listed possibility.)
For the case of 1st and 3rd Sunday and the 1st and 3rd Monday:
with parms (dt) as (values ( date '2020-04-01'), (date '2020-06-01') )
, base_dates( fsun, fmon) as
( select utl_dates_first_dow_of_month('Sun',dt)
, utl_dates_first_dow_of_month('Mon',dt)
from parms
)
select '1st & 3rd Sunday and 1st & 3rd Monday'
, fsun "1st Sunday"
, (fsun+interval '14 days')::date "3rd Sunday"
, fmon "1st Monday"
, (fmon+interval '14 days')::date "3rd Monday"
from base_dates;
For the 1st and 3rd Sunday of the month and the Monday following:
with parms (dt) as (values ( date '2020-04-01'), (date '2020-06-01') )
, base_dates( fsun, fmon) as
( select utl_dates_first_dow_of_month('Sun',dt)
, (utl_dates_first_dow_of_month('Sun',dt)+interval '1 day')::date
from parms
)
select '1st & 3rd Sunday and Monday Following '
, fsun "1st Sunday"
, fmon "1st Monday"
, (fsun+interval '14 days')::date "3rd Sunday"
, (fmon+interval '14 days')::date "3rd Monday"
from base_dates;
select * from
(
select dow, i, row_number() over(partition by dow order by i) as rnk from
(
select
extract(dow from i::date) as dow,
i::date
from generate_series('2022-10-01'::date,'2022-10-31'::date,interval '1 Day') i
) tmp where dow = 1
)tmp_out where rnk = 3;

How can I pull records for a full month AFTER a specific date?

I have a list of employees and their hire dates. I need to pull revenue records for their first 3 months on the job. For example, if someone is hired on August 6 2018, I need to pull their revenue records for September, October, and November 2018.
I really just need the "where" statement to find the correct dates.
Let's call the tables: bookings, employees
columns: hired_at, booked_at
I originally thought it was first 3 months from hire date, and I used
where bookings.booked_at <= dateadd(month, 3, employees.hire_date)
Upon finding out that I need the 3 months FOLLOWING the hire date, I'm not sure how to write this.
Thanks!
You should use INTERVAL, dateadd isn't supported by PostgreSQL
<= employees.hire_date - Interval '3 months'
You can check the documentation in the following link
https://www.postgresql.org/docs/9.0/functions-datetime.html
WHERE booked_at >= date_trunc('month', hired_at + interval '1 month')
AND booked_at < date_trunc('month', hired_at + interval '3 month')
where date_trunc('month', ...) always gives you the 1st of the month.

How do I find users created exactly multiple whole months/quarters ago in Postgres SQL?

How do I find all the user records created exactly multiple whole months/quarters ago in Postgres SQL? Is it possible?
In Postgres, you can find a date which is exactly 1 month away from a datetime field, like:
select created_at + interval '1 month' from users limit 1;
?column?
----------------------------
2016-10-05 17:05:14.811537
(1 row)
If today is 9/27/2016, I want to find all the user records created on 8/27/2016, 7/27/2016, 6/27/2016, ... 1/27/2015, etc, etc. How do I do this in SQL?
Note this isn't simply a comparison of date due to the fact that some months have 31 days while others have 28, 29, 30 days.
If today is 2/28/2015 (non-leap year), I want all the users created on the following dates: 1/28/2015, 1/29/2015, 1/30/2015, 1/31/2015, 12/28/2014, 12/29/2014, 12/30/2014, 12/31/2014, etc etc.
If today is 2/28/2016 (leap year), I want all the users created on the following dates: 1/28/2015, 12/28/2014, 11/28/2014, etc etc. (But not on 1/29/2015, 1/30/2015, 1/31/2015, as those users will be picked up the next day, see below).
If today is 2/29/2016 (leap year), I want all the users created on the following dates: 1/29/2015, 1/30/2015, 1/31/2015, 12/29/2014, 12/30/2014, 12/31/2014, etc etc.
If today is 3/31/2016, I want all the users created on 12/31/2015, 1/31/2016, but not anyone created in February 2016 because they would have been picked on previous days of March 2016.
How do I do the above with quarters instead of months?
Another question related to this is performance. If I create an index on created_at, would a whole table scan be avoided if I do this type of queries?
Thank you.
Well... If You think of it, You want to compare day number in reality, right? So You should do just that:
select
*
from
users
where
date_part('day', created_at) = date_part('day', current_timestamp)
OR
(
-- Check if this is the last day of month
extract(month from current_timestamp + '1day'::interval) <> extract(month from current_timestamp)
AND
date_part('day', created_at) > date_part('day', current_timestamp)
)
limit
1
;
And regarding Your index question - yes.
Inspiration for checking last day of a month taken from here.
Basically, if I still do not get Your requirement, You should be able to easily modify my code to meet it, if You understand it. :)

Get this week's monday's date in Postgres?

How can I get this week's monday's date in PostgreSQL?
For example, today is 01/16/15 (Friday). This week's monday date is 01/12/15.
You can use date_trunc() for this:
select date_trunc('week', current_date);
More details in the manual:
http://www.postgresql.org/docs/current/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
If "today" is Monday it will return today's date.
SELECT current_date + cast(abs(extract(dow FROM current_date) - 7) + 1 AS int);
works, although there might be more elegant ways of doing it.
The general idea is to get the current day of the week, dow, subtract 7, and take the abs, which will give you the number of days till the end of the week, and add 1, to get to Monday. This gives you next Monday.
EDIT: having completely misread the question, to get the prior Monday, is much simpler:
SELECT current_date - ((6 + cast(extract(dow FROM current_date) AS int)) % 7)
ie, subtract the current day of the week from today's date (the number of day's past Monday) and add one, to get back to Monday.
And for other mondays:
Next Monday:
date_trunc('week', now())+ INTERVAL '7days'
Last week's monday:
date_trunc('week', now())- INTERVAL '7days'
etc. :)
I usually use a calendar table. There are two main advantages.
Simple. Junior devs can query it correctly with little training.
Obvious. Correct queries are obviously correct.
Assuming that "this week's Monday" means the Monday before today, unless today is Monday then . . .
select max(cal_date) as previous_monday
from calendar
where day_of_week = 'Mon'
and cal_date <= current_date;

extract in postgresql date

I have a question about extract function in PostgreSQL.
I am generating reports about last month's data.
So far no problem:
where extract (month from date) = extract(month from current_date- '1 month'::interval)
But the problem with this setup is that when the data spans more than 1 year than more months will be included.
To handle this I can add another condition for year:
and extract (year from date) = extract (year from current_date)
But this will cause a problem when generating report about December in January.
How can I generate my report about December in January without the fear that I include more months.
How about this:
WHERE date_trunc('month',date)=date_trunc('month',current_date) - interval '1 month'