The title doesn't quite capture what I mean, and this may be a duplicate.
Here's the long version: given a guest's name, their registration date, and their checkout date, how do I generate one row for each day that they were a guest?
Ex: Bob checks in 7/14 and leaves 7/17. I want
('Bob', 7/14), ('Bob', 7/15), ('Bob', 7/16), ('Bob', 7/17)
as my result.
Thanks!
I would argue that for this specific purpose the below query is about as efficient as using a dedicated lookup table.
DECLARE @start DATE, @end DATE;
SELECT @start = '20110714', @end = '20110717';
;WITH n AS
(
SELECT TOP (DATEDIFF(DAY, @start, @end) + 1)
n = ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects
)
SELECT 'Bob', DATEADD(DAY, n-1, @start)
FROM n;
Results:
Bob 2011-07-14
Bob 2011-07-15
Bob 2011-07-16
Bob 2011-07-17
Presumably you'll need this as a set, not for a single member, so here is a way to adapt this technique:
DECLARE @t TABLE
(
Member NVARCHAR(32),
RegistrationDate DATE,
CheckoutDate DATE
);
INSERT @t SELECT N'Bob', '20110714', '20110717'
UNION ALL SELECT N'Sam', '20110712', '20110715'
UNION ALL SELECT N'Jim', '20110716', '20110719';
;WITH [range](d,s) AS
(
SELECT DATEDIFF(DAY, MIN(RegistrationDate), MAX(CheckoutDate))+1,
MIN(RegistrationDate)
FROM @t -- WHERE ?
),
n(d) AS
(
SELECT DATEADD(DAY, n-1, (SELECT MIN(s) FROM [range]))
FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects) AS s(n)
WHERE n <= (SELECT MAX(d) FROM [range])
)
SELECT t.Member, n.d
FROM n CROSS JOIN @t AS t
WHERE n.d BETWEEN t.RegistrationDate AND t.CheckoutDate;
----------^^^^^^^ not many cases where I'd advocate between!
Results:
Member d
-------- ----------
Bob 2011-07-14
Bob 2011-07-15
Bob 2011-07-16
Bob 2011-07-17
Sam 2011-07-12
Sam 2011-07-13
Sam 2011-07-14
Sam 2011-07-15
Jim 2011-07-16
Jim 2011-07-17
Jim 2011-07-18
Jim 2011-07-19
As @Dems pointed out, this could be simplified to:
;WITH natural AS
(
SELECT ROW_NUMBER() OVER (ORDER BY [object_id]) - 1 AS val
FROM sys.all_objects
)
SELECT t.Member, d = DATEADD(DAY, natural.val, t.RegistrationDate)
FROM @t AS t INNER JOIN natural
ON natural.val <= DATEDIFF(DAY, t.RegistrationDate, t.CheckoutDate);
I usually do this with a trick using row_number() on some table. So:
select t.name, dateadd(d, seq.seqnum, t.start_date)
from t left outer join
(select row_number() over (order by (select NULL)) as seqnum
from t
) seq
on seqnum <= datediff(d, t.start_date, t.end_date)
The calculation for seq goes pretty fast, since no calculation or ordering is required. However, you need to be sure the table is big enough for all time spans.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With