'How do I create a loop that generates future dates based on different frequencies and intervals until a specified end date?
I have a data set that appears like this:
| next_generation_date | procedure | interval | frequency |
|---|---|---|---|
| 2021-01-17 00:00:00.000 | Clean Restroom | 1 | day |
| 2021-01-17 00:00:00.000 | Vacuum | 2 | week |
| 2021-02-01 00:00:00.000 | Inspect Fire Extinguisher | 3 | month |
| 2021-10-01 00:00:00.000 | Test Generator | 1 | year |
My goal is to generate multiple date rows for each procedure by going off of the next_generation_date, interval and frequency columns until a specified end date. This specified end date would be the same date throughout the entire table. In this example, let's make the specified end date 2023-12-31. My end goal is for the table to appear similar to the below. Please note since this is an example, I didn't include every row as to avoid having hundreds of rows listed below.
| next_generation_date | procedure | interval | frequency |
|---|---|---|---|
| 2021-01-17 00:00:00.000 | Clean Restroom | 1 | day |
| 2021-01-18 00:00:00.000 | Clean Restroom | 1 | day |
| 2021-01-19 00:00:00.000 | Clean Restroom | 1 | day |
| 2021-01-20 00:00:00.000 | Clean Restroom | 1 | day |
| 2021-01-17 00:00:00.000 | Vacuum | 2 | week |
| 2021-01-31 00:00:00.000 | Vacuum | 2 | week |
| 2021-02-14 00:00:00.000 | Vacuum | 2 | week |
| 2021-02-28 00:00:00.000 | Vacuum | 2 | week |
| 2021-02-01 00:00:00.000 | Inspect Fire Extinguisher | 3 | month |
| 2021-05-01 00:00:00.000 | Inspect Fire Extinguisher | 3 | month |
| 2021-08-01 00:00:00.000 | Inspect Fire Extinguisher | 3 | month |
| 2021-11-01 00:00:00.000 | Inspect Fire Extinguisher | 3 | month |
| 2021-10-01 00:00:00.000 | Test Generator | 1 | year |
| 2022-10-01 00:00:00.000 | Test Generator | 1 | year |
| 2023-10-01 00:00:00.000 | Test Generator | 1 | year |
Below is my most recent attempt in Microsoft SQL Server Management Studio but when I execute the code, I'm presented with the following message: "Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <=, >, >= or when the subquery is used as an expression".
DECLARE @EndDate AS DATE = '2023-12-31'
SELECT [procedure], [frequency], [interval], [next_generation_date] FROM [data].[table]
WHILE (@EndDate > (SELECT [next_generation_date] FROM [data].[table]))
IF (SELECT [frequency] FROM [data].[table]) = 'day'
BEGIN
SELECT DATEADD(day, (SELECT [interval] FROM [data].[table]), (SELECT [next_generation_date] FROM [data].[table]))
END
ELSE IF (SELECT [frequency] FROM [data].[table]) = 'week'
BEGIN
SELECT DATEADD(week, (SELECT [interval] FROM [data].[table]), (SELECT [next_generation_date] FROM [data].[table]))
END
ELSE IF (SELECT [frequency] FROM [data].[table]) = 'month'
BEGIN
SELECT DATEADD(month, (SELECT [interval] FROM [data].[table]), (SELECT [next_generation_date] FROM [data].[table]))
END
ELSE IF (SELECT [frequency] FROM [data].[table]) = 'year'
BEGIN
SELECT DATEADD(year, (SELECT [interval] FROM [data].[table]), (SELECT [next_generation_date] FROM [data].[table]))
END
Any and all suggestions are greatly appreciated as I'm new to SQL. Thank you.
Solution 1:[1]
I would create a helper table that contains many integers, first.
CREATE TABLE many_integers (idx INT);
WITH units(units) AS (
SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION
SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
)
,tens AS(SELECT units * 10 AS tens FROM units )
,hundreds AS(SELECT tens * 10 AS hundreds FROM tens )
,thousands AS(SELECT hundreds * 10 AS thousands FROM hundreds )
,tenthousands AS(SELECT thousands * 10 AS tenthousands FROM thousands )
,hundredthousands AS(SELECT tenthousands * 10 AS hundredthousands FROM tenthousands)
INSERT
INTO many_integers
SELECT hundredthousands +tenthousands +thousands +hundreds +tens +units
FROM units
CROSS JOIN tens
CROSS JOIN hundreds
CROSS JOIN thousands
CROSS JOIN tenthousands
CROSS JOIN hundredthousands ;
Then cross join my in-data table with that many_integers table:
-- your input data, don't use in real query ...
WITH
indata(next_generation_date,proced,interv,frequency) AS (
SELECT '2021-01-17 00:00:00.000','Clean Restroom' ,1,'day'
UNION ALL SELECT '2021-01-17 00:00:00.000','Vacuum' ,2,'week'
UNION ALL SELECT '2021-02-01 00:00:00.000','Inspect Fire Extinguisher',3,'month'
UNION ALL SELECT '2021-10-01 00:00:00.000','Test Generator' ,1,'year'
)
-- end of your input data - real query starts here ...
SELECT
CASE frequency
WHEN 'day' THEN DATEADD( day , idx , next_generation_date)
WHEN 'week' THEN DATEADD( week , idx , next_generation_date)
WHEN 'month' THEN DATEADD( month , idx , next_generation_date)
WHEN 'year' THEN DATEADD( year , idx , next_generation_date)
END AS next_generation_date
, proced
, interv
, frequency
FROM indata CROSS JOIN many_integers
WHERE idx < 5000
AND
CASE frequency
WHEN 'day' THEN DATEADD( day , idx , next_generation_date)
WHEN 'week' THEN DATEADD( week , idx , next_generation_date)
WHEN 'month' THEN DATEADD( month , idx , next_generation_date)
WHEN 'year' THEN DATEADD( year , idx , next_generation_date)
END <= '2023-12-31 00:00:00'
ORDER BY 1
; ```
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
