Generate series of week intervals for given month - sql

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

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;

Get the timestamp of the last and first day of a month in postgresSQL

I am currently trying to figure out the equivalent functions in PostgresSQL for the below MariaDB functions:
SELECT
CONCAT(DATE_ADD(DATE_ADD(LAST_DAY('2021-02-15 00:00:00'),INTERVAL 1 DAY),INTERVAL -1 MONTH), ' ', '00:00:00') AS first_day_of_the_month,
CONCAT(LAST_DAY('2021-02-15 23:59:59'), ' ' ,'23:59:59') AS last_day_of_the_month;
Expected Result:
first_day_of_the_month | last_day_of_the_month
------------------------|-------------------------------
2021-02-01 00:00:00 | 2021-02-28 23:59:59
I want to get the last day and the first day of the month of a certain date including the time 00:00:00 for the first day and 23:549:59 for the last day.
So far I am comming close to the expected result with these queries:
SELECT
(date_trunc('MONTH', '2021-02-15 00:00:00'::TIMESTAMP) + INTERVAL '1 MONTH - 1 day')::TIMESTAMP
+ '1 days'::INTERVAL - '1 months'::INTERVAL AS first_day_of_the_month,
(date_trunc('MONTH', '2021-02-15 23:59:59'::TIMESTAMP) + INTERVAL '1 MONTH - 1 day')::TIMESTAMP AS last_day_of_the_month;
However, as you can see in the DB-Fiddle for the last_day_of_the_month I am getting 00:00:00 instead of 23:59:59.
How do I need to change the query to get the correct timestamp?
demo:db<>fiddle
/* Option 1: Certain Date */
SELECT
date_trunc('month', timestamp '2021-02-15 00:00:00') AS first_timestamp,
date_trunc('month', timestamp '2021-02-15 23:59:59') + interval '1 month - 1 second' AS last_timestamp;
/* Option 2: Current_date */
SELECT
date_trunc('month', current_date)::timestamp AS first_timestamp,
date_trunc('month', current_date)::timestamp + interval '1 month - 1 second' AS last_timestamp;
First timestamp of month
date_trunc('month', ...) normalizes the date to the first possible timestamp of the month. So it returns 00:00:00 of the first day of the month
Last timestamp of month
If you add a month to the previous result, you get the first day/timestamp (00:00:00) of the next month. If you subtract a day from this, you get the last day of the current month. Of course, you can subtract just a second to get 23:59:59 of the last day of the month as you expected.
For the first day of the month:
date_trunc('month', the_date_column)
For the last day:
date_trunc('month', the_date_column) + interval '1 month' - interval '1 day'
Or if you prefer:
date_trunc('month', date) + interval '1 month - 1 day'
If you really one one second from midnight on the last day, replace day with second in the logic. I don't recommend that. In fact, if you want to define a range of date, define an open range and just use the first day of the next month. Then you can learn about tsrange which builds this into the language.

Query filtering by week

How to query (SELECT) in Postgresql, so that the results of a column with different dates, are between Sunday and Saturday of the current week.
Query fake example:
SELECT * FROM table WHERE datecolumn BETWEEN CURRENT WEEK
In another query, I have the number of the week in the year. How to make a SELECT for these dates, applying in the WHERE clause the specific week number in the specific year.
Query fake example:
SELECT * FROM table WHERE datecolumn BETWEEN WEEK15 FROM year 2020
Perhaps you can use something like this:
SELECT *
FROM table
WHERE
EXTRACT(week FROM datecolumn) = EXTRACT(week FROM NOW())
AND
EXTRACT(isoyear FROM datecolumn) = EXTRACT(isoyear FROM NOW())
The week is ISO-8601 week number. By definition, ISO weeks start on Mondays and the first week of a year contains January 4 of that year. In other words, the first Thursday of a year is in week 1 of that year.
In the ISO week-numbering system, it is possible for early-January dates to be part of the 52nd or 53rd week of the previous year, and for late-December dates to be part of the first week of the next year.
For example, 2005-01-01 is part of the 53rd week of year 2004, and 2006-01-01 is part of the 52nd week of year 2005, while 2012-12-31 is part of the first week of 2013.
It's recommended to use the isoyear field together with week to get consistent results.
If you need custom (non-ISO) week numbering - you will have to craft your own calculation.
I would recommend the following pair of conditions:
where
date_column >= current_date - extract(dow from current_date) * interval '1 day'
and date_column < current_date - (extract(dow from current_date) - 8) * interval '1 day'
Postgres' date_trunc(week, ...) starts weeks on Monday, so we need something a little more complicated, using extract(dow from ...), which returns 0 on Sundays.
The advantage of this approach is that it is SARGeable, since no function is applied to the column being filtered. This means that this would happily take advantage of an index on the date column.
I would use date_trunc(), but like this. For the current week:
where datecolumn >= date_trunc('week', now()) and
datecolumn < date_trunc('week', now()) + interval '1 week'
For the nth week of the year, this is trickier. I think this does what you want:
where datecolumn >= (date_trunc('week', now()) -
(extract(week from now()) - 1) * interval '1 week' +
<n> * interval '1 week'
) and
datecolumn < (date_trunc('week', now()) -
(extract(week from now()) - 1) * interval '1 week' +
(<n> + 1) * interval '1 week'
)
Both of these are structured so the computations are NOT on the columns, so they are compatible with using indexes.

Get value zero if data is not there in PostgreSQL

I have a table employee in Postgres:
Query:
SELECT DISTINCT month_last_date,number_of_cases,reopens,csat
FROM employee
WHERE month_last_date >=(date('2017-01-31') - interval '6 month')
AND month_last_date <= date('2017-01-31')
AND agent_id='analyst'
AND name='SAM';
Output:
But if data is not in table for other month I want column value as 0.
Generate all dates you are interested in, LEFT JOIN to the table and default to 0 with COALESCE:
SELECT DISTINCT -- see below
i.month_last_date
, COALESCE(number_of_cases, 0) AS number_of_cases -- see below
, COALESCE(reopens, 0) AS reopens
, COALESCE(csat, 0) AS csat
FROM (
SELECT date '2017-01-31' - i * interval '1 mon' AS month_last_date
FROM generate_series(0, 5) i -- see below
) i
LEFT JOIN employee e ON e.month_last_date = i.month_last_date
AND e.agent_id = 'analyst' -- see below
AND e.name = 'SAM';
Notes
If you add or subtract an interval of 1 month and the same day does not exist in the target month, Postgres defaults to the latest existing day of that moth. So this works as desired, you get the last day of each month:
SELECT date '2017-12-31' - i * interval '1 mon' -- note 31
FROM generate_series(0,11) i;
But this does not, you'd get the 28th of each month:
SELECT date '2017-02-28' - i * interval '1 mon' -- note 28
FROM generate_series(0,11) i;
The safe alternative is to subtract 1 day from the first day of the next month, like #Oto demonstrated. Related:
Daily average for the month (needs number of days in month)
Here are two optimized ways to generate a series of last days of the month - up to and including a given month:
1.
SELECT (timestamp '2017-01-01' - i * interval '1 month')::date - 1 AS month_last_date
FROM generate_series(-1, 10) i; -- generate 12 months, off-by-1
Input is the first day of the month - or calculate it from a given date or timestamp with date_trunc():
SELECT date_trunc('month', timestamp '2017-01-17')::date AS this_mon1
Subtracting an interval from a date produces a timestamp. After the cast back to date we can simply subtract an integer to subtract days.
2.
SELECT m::date - 1 AS month_last_date
FROM generate_series(timestamp '2017-02-01' - interval '11 month' -- for 12 months
, timestamp '2017-02-01'
, interval '1 mon') m;
Input is the first day of the next month - or calculate it from any given date or timestamp with:
SELECT date_trunc('month', timestamp '2017-01-17' + interval '1 month')::date AS next_mon1
Related:
How do I determine the last day of the previous month using PostgreSQL?
Create list with first and last day of month for given period
Not sure you actually need DISTINCT. Typically, (agent_id, month_last_date) would be defined unique, then remove DISTINCT ...
Be sure to use the LEFT JOIN correctly. Join conditions go into the join clause, not the WHERE clause:
Explain JOIN vs. LEFT JOIN and WHERE condition performance suggestion in more detail
Finally, default to 0 with COALESCE where NULL values are filled in by the LEFT JOIN.
Note that COALESCE cannot distinguish between actual NULL values from the right table and NULL values filled in for missing rows. If your columns are not defined NOT NULL, there may be ambiguity to address.
As I see, you need generate last days of all last 6 months, before certain date. (before "2017-01-31" in this case).
If I correctly understand, then you can use this query, which generates all of these days
SELECT (date_trunc('MONTH', mnth) + INTERVAL '1 MONTH - 1 day')::DATE
FROM
generate_series('2017-01-31'::date - interval '6 month', '2017-01-31'::date, '1 month') as mnth;
You just need LEFT JOIN this query to your existing query, and you get desirable result
Please note that this will returns 7 record (days), not 6.

PostgreSQL Query to select data from last week?

I have a table which has all the purchases of my costumers. I want to select all entries from the last week, (week start from Sunday).
id value date
5907 1.20 "2015-06-05 09:08:34-03"
5908 120.00 "2015-06-09 07:58:12-03"
I've tried this:
SELECT id, valor, created, FROM compras WHERE created >= now() - interval '1 week' and parceiro_id= '1'
But I got the data from the last week including data from this week, I only want data from the last week.
How to get data only from last week ?
This condition will return records from Sunday till Saturday last week:
WHERE created BETWEEN
NOW()::DATE-EXTRACT(DOW FROM NOW())::INTEGER-7
AND NOW()::DATE-EXTRACT(DOW from NOW())::INTEGER
There is an example:
WITH compras AS (
SELECT ( NOW() + (s::TEXT || ' day')::INTERVAL )::TIMESTAMP(0) AS created
FROM generate_series(-20, 20, 1) AS s
)
SELECT to_char( created, 'DY'::TEXT), created
FROM compras
WHERE created BETWEEN
NOW()::DATE-EXTRACT(DOW FROM NOW())::INTEGER-7
AND NOW()::DATE-EXTRACT(DOW from NOW())::INTEGER
In answer to #d456:
Wouldn't using BETWEEN include midnight on Sunday at both ends of the interval?
That right, BETWEEN includes midnight on Sunday at both ends of the interval. To exclude midnight on Sunday at end of interval it is necessary to use operators >= and <:
WITH compras AS (
SELECT s as created
FROM generate_series( -- this would produce timestamps with 20 minutes step
(now() - '20 days'::interval)::date,
(now() + '20 days'::interval)::date,
'20 minutes'::interval) AS s
)
SELECT to_char( created, 'DY'::TEXT), created
FROM compras
WHERE TRUE
AND created >= NOW()::DATE-EXTRACT(DOW FROM NOW())::INTEGER-7
AND created < NOW()::DATE-EXTRACT(DOW from NOW())::INTEGER
Postgres by default starts weeks on a Sunday, so you are in luck. You can use date_trunc() to get the beginning of the previous week:
WHERE (created >= date_trunc('week', CURRENT_TIMESTAMP - interval '1 week') and
created < date_trunc('week', CURRENT_TIMESTAMP)
)
EDIT:
Postgres by default starts week for date_trunc on Monday, but for dow on Sunday. So, you can do what you want by using that logic, which Nicolai has in his answer.