Generate calendar with postgresql - date

I'm trying to write query which shows calendar in format:
mon. thues. wen. th. fri. sat. sun.
First value in current week is always monday.
For instance, today is friday - 24-07-2015, so i need first value to be 20-07-2015 and the last one 26-07-2015
Until now, I came up with this:
SELECT
extract(DOW FROM r.e - s.a) w,
r.e - s.a as dates
FROM generate_series(0,7,1) AS s(a),
(SELECT CURRENT_DATE e) r
WHERE extract(DOW FROM CURRENT_DATE - s.a) >=1
GROUP BY 1,2;
and then I could sort it with distinct and then substract sunday in the same way. But maybe you can suggest some more elegant solution?
Basically I would like to find the date of closest monday and closest sunday.

Ok, I've managed it:
WITH monday AS (
SELECT
r.e - s.a as dow
FROM generate_series(0,6,1) AS s(a),
(SELECT CURRENT_DATE e) r
WHERE extract(DOW FROM CURRENT_DATE - s.a) = 1
), sunday as (
SELECT
r.e + s.a as dow
FROM generate_series(0,6,1) AS s(a),
(SELECT CURRENT_DATE e) r
WHERE extract(DOW FROM CURRENT_DATE + s.a) = 0
), columns AS(
SELECT n::date
FROM generate_series((select dow from monday), (select dow from sunday), $$1 day$$) n
) SELECT n FROM columns;

Related

extract days of daterange grouped by month postresql

I have a pickupDate and returnDate in my OrderHistory table. I want to extract the sum of rental days of all OrderHistory entries, grouped/ordered by month. A cte seems to be the solution but I don´t get how to implement it in my query since the cte´s i saw were refering to themselves where it says "FROM cte".
I tried something like this:
SELECT
SUM((EXTRACT (DAY FROM("OrderHistory"."returnDate")-("OrderHistory"."pickupDate")))) as traveltime
, to_char("OrderHistory"."pickupDate"::date, 'YYYY-MM') as M
FROM
"OrderHistory"
GROUP BY
M
ORDER BY
M
But the outcome doesn´t split bookings btw two months (e.g. pickupDate=27th march 2022 and returnDate=03rd of april 2022) but will assign the whole 7 days to the month of march, since the returndate is in it. It should show 4 days in march and 3 in april.
Sorry for the probably very stupid question but I am a beginner. (my code is written in postgresql btw)
PostgreSQL naming conventions
Are PostgreSQL column names case-sensitive?
use legal, lower-case names exclusively so double-quoting is not
needed.
Final result in db fiddle
Add daterange column.
alter table order_history add column date_ranges daterange;
update order_history
with a(m_begin, m_end, pickup_date) as
(select date_trunc('month', pickup_date)::date,
(date_trunc('month', pickup_date) + interval '1 month - 1 day')::date,
pickup_date from order_history)
update order_history set date_ranges =
daterange(a.m_begin, a.m_end,'[]') from a
where a.pickup_date = order_history.pickup_date;
then final query:
WITH A AS(
select
pickup_date,
return_date,
return_date - pickup_date as total,
case when return_date <# date_ranges then (return_date - pickup_date)
else ( date_trunc('month', pickup_date) + interval '1 month - 1 day')::date - pickup_date
end partial_mth
from order_history),
b as (SELECT *, a.total - partial_mth parital_not_mth FROM a)
select *,
case when to_char(pickup_date,'YYYY-MM') = to_char(return_date,'YYYY-MM')
then
sum(partial_mth) over(partition by to_char(pickup_date,'YYYY-MM')) +
sum(parital_not_mth) over (partition by to_char(return_date,'YYYY-MM'))
else sum(partial_mth) over(partition by to_char(pickup_date,'YYYY-MM'))
end
from b;
After trying different things I think I found the best answer to my question, that I want to share with the community:
WITH hier as (
SELECT
"OrderHistory"."pickupDate" as start_date
, "OrderHistory"."returnDate" as end_date
, to_char("OrderHistory"."pickupDate"::date, 'YYYY-MM') as M
FROM
"OrderHistory"
GROUP BY
1, 2, 3
ORDER BY
3
), calendar as (
select date '2022-01-01' + (n || ' days')::interval calendar_date
from generate_series(0, 365) n
)
select
to_char(calendar_date::date, 'YYYY-MM')
, count(*) as tage_gebucht
from calendar
inner join hier on calendar.calendar_date between start_date and end_date
where calendar_date between '2022-01-01' and '2022-12-31'
group by 1
order by 1;
I think this is the simplest solution I came up with.

To get weekday between two date in Postgresql

I need to get all week days in a given time interval.
In postgresql, there are dow and isodow
By mixing them together, may I write a function to retrieve weekdays?
demo:db<>fiddle
SELECT
generated_date,
to_char(generated_date, 'Day'), -- 1
EXTRACT(isodow FROM generated_date), -- 2
EXTRACT(dow FROM generated_date) -- 3
FROM
generate_series('2020-11-01'::date, '2020-11-10'::date, interval '1 day') AS generated_date
Returns the name for the weekday
Returns the number of the weekday (Monday = 1, Sunday = 7)
Returns the number of the weekday (Sunday = 0, Saturday = 6)
Edit:
If you want to get the days without weekend, you can filter by the dow/isodow values, e.g.:
SELECT
generated_date::date
FROM
generate_series('2020-11-01'::date, '2020-11-10'::date, interval '1 day') AS generated_date
WHERE
EXTRACT(isodow FROM generated_date) < 6
As far as I understand you need to extract all Monday..Fridays between two dates. Here is an illustration with 2020-11-30 as the beginning of the interval and 2020-12-12 as the end of it.
select d
from generate_series('2020-11-30'::date, '2020-12-12'::date, '1 day'::interval) t(d)
where extract(isodow from d) between 1 and 5;

Compare day in current month to same day previous month PostgreSQL

I'm trying to compare values of current month's data to previous months using PostgreSQL. So if today is 4/23/2018, I want the data for 3/23/2018.
I've tried current_date - interval '1 month' but it is problematic for months with 31 days.
My table is structured as simply as
date, value
Check this example query:
WITH dates AS (SELECT date::date FROM generate_series('2018-01-01'::date, '2018-12-31'::date, INTERVAL '1 day') AS date)
SELECT
start_dates.date AS start_date,
end_dates.date AS end_date
FROM
dates AS start_dates
RIGHT JOIN dates AS end_dates
ON ( start_dates.date + interval '1 month' = end_dates.date AND
end_dates.date - interval '1 month' = start_dates.date);
It will output all end_dates and corresponding start_dates. The corresponding dates are defined by interval '1 month' and checked in both ways:
start_dates.date + interval '1 month' = end_dates.date AND
end_dates.date - interval '1 month' = start_dates.date
The output looks like this:
....
2018-02-26 2018-03-26
2018-02-27 2018-03-27
2018-02-28 2018-03-28
2018-03-29
2018-03-30
2018-03-31
2018-03-01 2018-04-01
2018-03-02 2018-04-02
2018-03-03 2018-04-03
2018-03-04 2018-04-04
....
Note, that there are 'gaps' for days without corresponding dates.
Back to your table, join the table with itself (giving aliases) and use given join condition, so the query would look like this:
SELECT
start_dates.value - end_dates.value AS change,
start_dates.date AS start_date,
end_dates.date AS end_date
FROM
_your_table_name_ AS start_dates
RIGHT JOIN _your_table_name_ AS end_dates
ON ( start_dates.date + interval '1 month' = end_dates.date AND
end_dates.date - interval '1 month' = start_dates.date);
Given the following table structure:
create table t (
d date,
v int
);
After populating with some dates and values, there is a way to find the value of the previous month using simple calculations and the LAG function, without resorting to joins. I am not sure how it compares from a performance perspective, so please run your own tests before selecting which solution to use.
select
*,
lag(v, day_of_month) over (order by d) as v_end_of_last_month,
lag(v, last_day_of_previous_month + day_of_month - cast(extract(day from d - interval '1 month') as int)) over (order by d) as v_same_day_last_month
from (
select
*,
lag(day_of_month, day_of_month) over (order by d) as last_day_of_previous_month
from (
select
*,
cast(extract(day from d) as int) as day_of_month
from
t
) t_dom
) t_dom_ldopm;
You may note that between the 29th and 31st of March, the comparison will be made against the 28th of February, since the same day does not exist in February for those particular dates. The same logic applies to other months with different number of days.

Count records grouped by day that counted by interval

Here is the query
WITH dates AS (
SELECT current_date - serie AS date
FROM generate_series(0, 365, 1) AS serie
), items AS (
SELECT *
FROM items
WHERE created_at BETWEEN now() - interval '6 months' AND now()
)
SELECT dates.date, count(items)
FROM dates
LEFT OUTER JOIN items ON items.created_at::date = dates.date
GROUP BY dates.date
Everything works fine except one thing - I need to somehow replace now() with day in a row.
So for each day calculate items count with conditions based on that day.
Just can't reference it.
Is there any solution for this?
smth like this?
WITH dates AS (
SELECT current_date - serie AS date
FROM generate_series(0, 365, 1) AS serie
)
SELECT dates.date, count(items)
FROM dates
LEFT OUTER JOIN items ON created_at BETWEEN dates.date- interval '6 months' AND dates.date
GROUP BY dates.date;
I came to the following solution, which has the same result as Vao Tsun proposed:
WITH dates AS (
SELECT current_date - serie AS date
FROM generate_series(0, 365, 1) AS serie
), date_intervals AS (
SELECT
(dates.date - INTERVAL '6 months') AS start_date,
dates.date AS end_date
FROM dates
)
SELECT date_intervals.end_date, count(items)
FROM date_intervals
LEFT OUTER JOIN items ON items.created_at BETWEEN date_intervals.start_date AND date_intervals.end_date
GROUP BY 1
ORDER BY 1

Creating a PostgreSQL `tsrange` from two timestamps

I am trying to create a tsrange (last Thursday to the previous Thursday) in a postgresql query but I get cast errors.
This is what I have got so far (starting off from this SO question).
WITH past_week AS (
SELECT date_trunc('day', NOW() + (s::TEXT || ' day')::INTERVAL)::TIMESTAMP(0) AS day
FROM generate_series(-7, 0, 1) AS s)
SELECT (
date_trunc('day', (SELECT day FROM past_week WHERE EXTRACT(DOW FROM day) = '4') - '7 day'::INTERVAL),
date_trunc('day', (SELECT day FROM past_week WHERE EXTRACT(DOW FROM day) = '4')));
And this is the result (correct value, but not format, since it's not a range):
row
-----------------------------------------------
("2015-10-29 00:00:00","2015-11-05 00:00:00")
(1 row)
Now, there are 2 main things that bug me:
If I try and add a ::tsrange right before the end of the query, the interpreter complains that:
ERROR: cannot cast type record to tsrange
LINE 6: ...ROM past_week WHERE EXTRACT(DOW FROM day) = '4')))::tsrange;
I would love to avoid repetition, but I'm not that proficient in SQL to know how. Any improvement is more than welcome.
Use tsrange() constructor:
WITH past_week AS (
SELECT date_trunc('day', NOW() + (s::TEXT || ' day')::INTERVAL)::TIMESTAMP(0) AS day
FROM generate_series(-7, 0, 1) AS s)
SELECT tsrange(
date_trunc('day',
(SELECT day FROM past_week
WHERE EXTRACT(DOW FROM day) = '4') - '7 day'::INTERVAL),
date_trunc('day',
(SELECT day FROM past_week
WHERE EXTRACT(DOW FROM day) = '4')));
tsrange
-----------------------------------------------
["2015-10-29 00:00:00","2015-11-05 00:00:00")
(1 row)
Using CURRENT_DATE your query may be as simple as:
WITH previous_thursday AS (
SELECT CURRENT_DATE- EXTRACT(DOW FROM CURRENT_DATE)::int+ 4 AS thursday
)
SELECT tsrange(thursday- '7d'::INTERVAL, thursday)
FROM previous_thursday;