How to generate a series but keep the start and end date?

ghz 8months ago ⋅ 63 views

Edited* Using psql 9.4.26

I have this table:

KeyStartEnd
ABC12324/01/201223/01/2013

And using psql, I'm trying to get an output similar to:

KeyStartEnd
ABC12324/01/201231/01/2012
ABC1231/02/201229/02/2012
ABC1231/03/201231/03/2012
ABC1231/04/201230/04/2012
ABC1231/05/201231/05/2012
ABC1231/06/201230/06/2012
ABC1231/02/201231/07/2012
ABC1231/08/201231/08/2012
ABC1231/09/201230/09/2012
ABC1231/10/201231/10/2012
ABC1231/11/201230/11/2012
ABC1231/12/201231/12/2012
ABC1231/01/201323/01/2013

I queried the following:

WITH date_ranges AS (
    SELECT
        TO_DATE("Start", 'DD/MM/YYYY') AS start_date,
        TO_DATE("End", 'DD/MM/YYYY') AS end_date
    FROM
      tabl
)
SELECT
    TO_CHAR(month_start, 'DD/MM/YYYY') AS Start,
    TO_CHAR(LEAST(month_start + INTERVAL '1 month' - INTERVAL '1 day', end_date), 'DD/MM/YYYY') AS End
FROM
    date_ranges
CROSS JOIN LATERAL generate_series(
    date_trunc('month', start_date),
    LEAST(date_trunc('month', end_date) + INTERVAL '1 month' - INTERVAL '1 day', end_date),
    INTERVAL '1 month'
) AS month_start(month_start);

However, the first record output is this:

KeyStartEnd
ABC1231/01/201231/01/2012

The outcome should be:

KeyStartEnd
ABC12324/01/201231/01/2012

Answers

To achieve the desired output where the start date begins from the actual start date in the table rather than the first day of the month, you can adjust the generate_series function accordingly. Here's the modified query:

WITH date_ranges AS (
    SELECT
        TO_DATE("Start", 'DD/MM/YYYY') AS start_date,
        TO_DATE("End", 'DD/MM/YYYY') AS end_date
    FROM
      tabl
)
SELECT
    TO_CHAR(start_date, 'DD/MM/YYYY') AS Start,
    TO_CHAR(LEAST(start_date + INTERVAL '1 month' - INTERVAL '1 day', end_date), 'DD/MM/YYYY') AS End
FROM
    date_ranges
UNION ALL
SELECT
    TO_CHAR(month_start, 'DD/MM/YYYY') AS Start,
    TO_CHAR(LEAST(month_start + INTERVAL '1 month' - INTERVAL '1 day', end_date), 'DD/MM/YYYY') AS End
FROM
    date_ranges
CROSS JOIN LATERAL generate_series(
    date_trunc('month', start_date + INTERVAL '1 month'),
    LEAST(date_trunc('month', end_date) + INTERVAL '1 month' - INTERVAL '1 day', end_date),
    INTERVAL '1 month'
) AS month_start(month_start);

In this modified query:

  1. I've added a UNION ALL clause to include the initial period from the actual start date to the end of the first month.
  2. The generate_series function now starts from the beginning of the second month onwards to ensure that the first period starts from the actual start date in the table.
  3. The start_date is used as the start date for the first period instead of the truncated month.