All we need is an easy explanation of the problem, so here it is.
Let’s say we have a small inn. People make reservations online. I receive a reservation number, the date they will arrive and the length of the stay. I would like not just the number of days of their stay, but the actual calendar dates as well. For example, if my source table looks like this:
1000|5|6/5/2021
1001|3|6/7/2021
Then I would expect the output to be:
1000|6/5/2021
1000|6/6/2021
1000|6/7/2021
1000|6/8/2021
1000|6/9/2021
1001|6/7/2021
1001|6/8/2021
1001|6/9/2021
I’ve been using the following code to generate dates, but I am having trouble joining it back to the original data..
DECLARE @StartDate DATE, @EndDate DATE
SELECT @StartDate = '2021-11-01', @EndDate = '2021-12-01';
WITH ListDates(AllDates) AS
( SELECT @StartDate AS DATE
UNION ALL
SELECT DATEADD(DAY,1,AllDates)
FROM ListDates
WHERE AllDates < @EndDate)
SELECT AllDates
FROM ListDates
GO
How to solve :
I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.
Method 1
You are very close to it .
(Usually, there is a calendar table and used it , instead of the recursive query. You can search on web for an example)
I don’t know if you received a text/string (for the source of your data) or it’s table . In this solution , I used a table (@tv_Source)
Here is one way :
DECLARE @StartDate DATE, @EndDate DATE
DECLARE @tv_Source TABLE
(
id int, noDays int,startDate date
);
INSERT INTO @tv_Source(id,noDays,startDate)
VALUES(1000,5,'6/5/2021'),
(1001,3,'6/7/2021');
SELECT @StartDate = '2021-06-01', @EndDate = '2021-07-01';
WITH ListDates(AllDates) AS
( SELECT @StartDate AS DATE
UNION ALL
SELECT DATEADD(DAY,1,AllDates)
FROM ListDates
WHERE AllDates < @EndDate)
SELECT AllDates,s.id
FROM ListDates as l
INNER JOIN @tv_Source as s
ON l.AllDates >=s.startDate
AND l.AllDates< DATEADD(dAY,s.noDays,s.startDate)
ORDER BY s.id ASC,l.AllDates ASC
OPTION (MAXRECURSION 0)
output:
AllDates id
2021-06-05 1000
2021-06-06 1000
2021-06-07 1000
2021-06-08 1000
2021-06-09 1000
2021-06-07 1001
2021-06-08 1001
2021-06-09 1001
Method 2
You can do the following (all the code below is available on the fiddle here):
CREATE TABLE booking
(
bk_no INTEGER NOT NULL PRIMARY KEY,
bk_nights INTEGER NOT NULL CHECK (bk_nights > 0 AND bk_nights < 100), -- 100 arbitrary
bk_sdt DATE NOT NULL
);
Populate the table:
INSERT INTO booking
VALUES
(1000, 5, '2021-05-06'),
(1001, 3, '2021-05-06');
And create a new table to hold our booking nights:
CREATE TABLE booking_nights
(
nt_no INTEGER NOT NULL,
bk_no INTEGER NOT NULL,
bk_night DATE NOT NULL,
PRIMARY KEY (bk_no, bk_night)
);
Then run the following query – it’s a RECURSIVE CTE
:
WITH cte (n, bkno, bknt, bkdt) AS
(
SELECT
1,
bk_no,
bk_nights,
bk_sdt
FROM booking
UNION ALL
SELECT
n + 1,
bkno,
bknt,
DATEADD(DAY, 1, bkdt)
FROM cte
WHERE n < bknt
)
INSERT INTO booking_nights
SELECT c.n, c.bkno, c.bkdt
FROM cte c
ORDER BY c.bkno, c.bkdt;
and then:
SELECT * FROM booking_nights
ORDER BY bk_no, bk_night;
Result:
nt_no bk_no bk_night
1 1000 2021-05-06
2 1000 2021-05-07
3 1000 2021-05-08
4 1000 2021-05-09
5 1000 2021-05-10
1 1001 2021-05-06
2 1001 2021-05-07
3 1001 2021-05-08
8 rows
A note on the CHECK
constraint part of the table definition, i.e.:
bk_nights INTEGER NOT NULL CHECK (bk_nights > 0 AND bk_nights < 100), -- 100 arbitrary
-
with the use of this constraint in conjuction with the
n
variable in the main RCTE, the maximum number of iterations will be 100. Not having a limit on this risks going into an infinite loop in the event of some data error! -
setting
OPTION (MAXRECURSION 0);
is dangerous, because that brake is deactivated (dbfiddle will eventually give aRun failed
error).An alternative might be to set
MAXRECURSION
to something like 100 and not have the constraint in the table definition, but to "drive" without brakes is never advisable. The maximum this can be set to is32,767
(the highest possible value of a signed 32bit INTEGER). This is discussed in another answer of mine here.
A performance analysis is available here.
Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0