Reputation: 1922
Background (very much simplified): I have banner space that I sell on a website. A banner can be booked for a given start and end date, this banner cannot be double booked. The banner can be booked multiple times in a year but never more than once on the same date.
My problem: When saving a booking, the user enters a start / end date, now a banner may be booked multiple times within this start /end date and I want to automatically book any free dates within the start / end date.
An example:
s|aaaaaaa|s1------e1|aaaaaaaaa|s2------e2|aaaaaa|e
s / e = booking dates entered by user. s1 - e1 = first booking. s2 - e2 = second booking. aaa.. = available date ranges.
In tsql I want to work out the start/end dates for the aaa.. (available dates ranges). Then insert them into the booking table.
The booking table looks like this:
BookingId------BannerId-----StartDate-----ExpiryDate
000000001------00000001-----2012-11-01----2012-11-05
000000002------00000001-----2012-11-10----2012-11-15
000000003------00000001-----2012-11-16----2012-11-20
000000004------00000001-----2012-12-01----2012-12-05
So, if a user enters a start date of 2012-11-04 and an end date of 2012-12-10. The available dates would be.. 2012-11-06 to 2012-11-09, 2012-11-21 to 2012-11-30, 2012-12-06-2012-12-10.
Thanks for any advice on how to do this.
Upvotes: 1
Views: 314
Reputation: 239824
This seems to do the trick:
(All one script, but split so that you can see each part) Data setup:
declare @Bookings table (
BookingId char(9) not null,
BannerId char(8) not null,
StartDate date not null,
ExpiryDate date not null
)
insert into @Bookings (BookingId,BannerId,StartDate,ExpiryDate) values
('000000001','00000001','20121101','20121105'),
('000000002','00000001','20121110','20121115'),
('000000003','00000001','20121116','20121120'),
('000000004','00000001','20121201','20121205')
Input:
declare @StartDate date
declare @EndDate date
select @StartDate = '20121104',@EndDate = '20121210'
Query:
;With Ordered as (
select *,ROW_NUMBER() OVER (PARTITION BY BannerID ORDER BY StartDate) as rn
from @Bookings
), FreePeriods as (
select
o1.BannerId,
DATEADD(day,1,o1.ExpiryDate) as StartDate,
DATEADD(day,-1,o2.StartDate) as EndDate
from
Ordered o1
inner join
Ordered o2
on
o1.BannerId = o2.BannerId and
o1.rn = o2.rn - 1
where
DATEDIFF(day,o1.ExpiryDate,o2.StartDate) >= 3
union all
select
BannerId,'00010101',DATEADD(day,-1,MIN(StartDate)) from @Bookings group by BannerID
union all
select
BannerID,DATEADD(day,1,MAX(ExpiryDate)),'99991231' from @Bookings group by BannerID
)
select
BannerID,
CASE WHEN @StartDate > StartDate then @StartDate ELSE StartDate END as StartDate,
CASE WHEN @EndDate < EndDate then @EndDate ELSE EndDate END as EndDate
from
FreePeriods fp
where
fp.EndDate >= @StartDate and
fp.StartDate <= @EndDate
Basically, I organize the booked dates into successive pairs, then use each pair to construct a free period that exists between them. I also fake two more periods - one which runs from the beginning of time until the earliest booking, one which runs from the latest booking to the end of time.
I then, in the final query, find periods which overlap the requested dates, and use some CASE
expressions to trim periods that extend beyond the requested dates.
Upvotes: 1