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