Calendar table!
Another perfect candidate for a calendar table. If you can not find it, here, which I did earlier .
Setting data
DECLARE @t_venues table ( venue_id int , start_date date , end_date date ); INSERT INTO @t_venues (venue_id, start_date, end_date) VALUES (1, '2014-01-01', '2014-01-02') , (2, '2014-01-05', '2014-01-05') , (3, '2014-01-09', '2014-01-15') , (4, '2014-01-20', '2014-01-30') ; DECLARE @t_venueuser table ( venue_id int , start_date date , end_date date ); INSERT INTO @t_venueuser (venue_id, start_date, end_date) VALUES (1, '2014-01-02', '2014-01-02') , (2, '2014-01-05', '2014-01-05') , (3, '2014-01-09', '2014-01-10') , (4, '2014-01-23', '2014-01-25') ;
Inquiry
SELECT t_venues.venue_id , calendar.the_date , CASE WHEN t_venueuser.venue_id IS NULL THEN 1 ELSE 0 END As is_available FROM dbo.calendar INNER JOIN @t_venues As t_venues ON t_venues.start_date <= calendar.the_date AND t_venues.end_date >= calendar.the_date LEFT JOIN @t_venueuser As t_venueuser ON t_venueuser.venue_id = t_venues.venue_id AND t_venueuser.start_date <= calendar.the_date AND t_venueuser.end_date >= calendar.the_date ORDER BY t_venues.venue_id , calendar.the_date ;
Result
venue_id the_date is_available ----------- ----------------------- ------------ 1 2014-01-01 00:00:00.000 1 1 2014-01-02 00:00:00.000 0 2 2014-01-05 00:00:00.000 0 3 2014-01-09 00:00:00.000 0 3 2014-01-10 00:00:00.000 0 3 2014-01-11 00:00:00.000 1 3 2014-01-12 00:00:00.000 1 3 2014-01-13 00:00:00.000 1 3 2014-01-14 00:00:00.000 1 3 2014-01-15 00:00:00.000 1 4 2014-01-20 00:00:00.000 1 4 2014-01-21 00:00:00.000 1 4 2014-01-22 00:00:00.000 1 4 2014-01-23 00:00:00.000 0 4 2014-01-24 00:00:00.000 0 4 2014-01-25 00:00:00.000 0 4 2014-01-26 00:00:00.000 1 4 2014-01-27 00:00:00.000 1 4 2014-01-28 00:00:00.000 1 4 2014-01-29 00:00:00.000 1 4 2014-01-30 00:00:00.000 1 (21 row(s) affected)
Explanation
Our calendar tables contain an entry for each date.
We join our t_venues (alternatively, if you have a choice, lose the t_ prefix) so that it returns between our start_date and end_date every day. Sample output for venue_id=4 for this connection only:
venue_id the_date ----------- ----------------------- 4 2014-01-20 00:00:00.000 4 2014-01-21 00:00:00.000 4 2014-01-22 00:00:00.000 4 2014-01-23 00:00:00.000 4 2014-01-24 00:00:00.000 4 2014-01-25 00:00:00.000 4 2014-01-26 00:00:00.000 4 2014-01-27 00:00:00.000 4 2014-01-28 00:00:00.000 4 2014-01-29 00:00:00.000 4 2014-01-30 00:00:00.000 (11 row(s) affected)
Now we have one row per day, we [external] join our table t_venueuser . We join this almost the same way as before, but with one twist added: we also need to join venue_id !
Running this for venue_id=4 gives the following result:
venue_id the_date t_venueuser_venue_id ----------- ----------------------- -------------------- 4 2014-01-20 00:00:00.000 NULL 4 2014-01-21 00:00:00.000 NULL 4 2014-01-22 00:00:00.000 NULL 4 2014-01-23 00:00:00.000 4 4 2014-01-24 00:00:00.000 4 4 2014-01-25 00:00:00.000 4 4 2014-01-26 00:00:00.000 NULL 4 2014-01-27 00:00:00.000 NULL 4 2014-01-28 00:00:00.000 NULL 4 2014-01-29 00:00:00.000 NULL 4 2014-01-30 00:00:00.000 NULL (11 row(s) affected)
See how we have NULL for rows where there is no t_venueuser . Genius, no? ;-)
So, in my first query, I gave you a quick CASE statement showing availability (1 = available, 0 = not available). This is for illustration purposes only, but may be useful to you.
Then you can either wrap the query and then apply an additional filter in this computed column or simply add the where WHERE t_venueuser.venue_id IS NULL to: WHERE t_venueuser.venue_id IS NULL , and this will do the same trick.