简体   繁体   中英

SQL to get a row for start and end date for each year given a start date and number of years

I have the following data in a SQL table:

+------------------------------------+
| ID          YEARS       START_DATE |
+------------------------------------+
| ----------- ----------- ---------- |
| 1           5           2020-12-01 |
| 2           8           2020-12-01 |
+------------------------------------+

Trying to create a SQL that would expand the above data and give me a start and end date for each year depending on YEARS and START_DATE from above table. Sample output below:

+-----------------------------------------------+
|  ID          YEAR        DATE_START DATE_END  |
+-----------------------------------------------+
| ----------- ----------- ---------- ---------- |
| 1           1           2020-12-01 2021-11-30 |
| 1           2           2021-12-01 2022-11-30 |
| 1           3           2022-12-01 2023-11-30 |
| 1           4           2023-12-01 2024-11-30 |
| 1           5           2024-12-01 2025-11-30 |
| 2           1           2020-12-01 2021-11-30 |
| 2           2           2021-12-01 2022-11-30 |
| 2           3           2022-12-01 2023-11-30 |
| 2           4           2023-12-01 2024-11-30 |
| 2           5           2024-12-01 2025-11-30 |
| 2           6           2025-12-01 2026-11-30 |
| 2           7           2026-12-01 2027-11-30 |
| 2           8           2027-12-01 2028-11-30 |
+-----------------------------------------------+

I would use an inline tally for this, as they are Far faster than a recursive CTE solution. Assuming you have low values for Years :

WITH YourTable AS(
    SELECT *
    FROM (VALUES(1,5,CONVERT(date,'20201201')),
                (2,8,CONVERT(date,'20201201')))V(ID,Years, StartDate))
SELECT ID,
       V.I + 1 AS [Year],
       DATEADD(YEAR, V.I, YT.StartDate) AS StartDate,
       DATEADD(DAY, -1, DATEADD(YEAR, V.I+1, YT.StartDate)) AS EndDate
FROM YourTable YT
     JOIN (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10))V(I) ON YT.Years > V.I;

If you have more than 10~ years you can use either create a tally table, or create an large one inline in a CTE. This would start as:

WITH N AS(
    SELECT N
    FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I --remove the -1 if you don't want to start from 0
    FROM N N1, N N2) --100 rows, add more Ns for more rows
...

Of course, I doubt you have 1,000 of years of data.

You can use a recursive CTE:

with cte as (
      select id, 1 as year, start_date,
             dateadd(day, -1, dateadd(year, 1, start_date)) as end_date,
             years as num_years
      from t
      union all
      select id, year + 1, dateadd(year, 1, start_date),
             dateadd(day, -1, dateadd(year, 1, start_date)) as end_date,
             num_years             
      from cte
      where year < num_years
     )
select id, year, start_date, end_date
from cte;

Here is a db<>fiddle.

In a query, you can use the following:

DATEADD(YEAR, 1, DATE_START) - 1

to add this to the table you can just create the extra column, and set it equal to the value of the above, eg

UPDATE MyTable
SET DATE_END = DATEADD(YEAR, 1, DATE_START) - 1

If you are working with sql server, then you can try to use operator CROSS APPLY with master.dbo.spt_values table to get list of numbers and generate dates:

 select ID,T.number+1 as YEAR, 
 --generate date_start using T.number
 dateadd(year,T.number,START_DATE)date_start,
 --generate end_date: adding 1 year to start date  
 dateadd(dd,-1,dateadd(year,1,dateadd(year,T.number,START_DATE)))date_end 
 from Table
 cross apply
 master.dbo.spt_values T
 where T.type='P' and T.number<YEARS

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM