views:

181

answers:

2

For a reservation system there is an inventory table and each item has a quantity (e.g. there are 20 chairs). Now the user can make a reservation for a specific period (e.g. 5 chairs for two hours "2010-11-23 15:00" - "2010-11-23 17:00"; another reservation could be for several days "2010-11-24 11:00" - "2010-11-26 14:00").

What's the best way to check, how many items are still available for the requested period?

The user should enter the time he wants to make a reservation (from, until) and he should see how many inventory items are still available for this period.

table "inventory"
-------------------
inventory_id (int) 
quantity (int)

table "reservation"
-------------------
reservation_id (int)
inventory_id (int)
quantity (int)
from (datetime)
until (datetime)

The reservations can be overlapping, but for a point in time, only inventory.quantity items should be reserved.

Simple Example:

We have 40 chairs.

The following reservations exist:

R1 2010-11-23 14:00 - 2010-11-23 15:30 -> 5 chairs reserved
R2 2010-11-23 15:00 - 2010-11-23 16:00 -> 10 chairs reserved
R3 2010-11-23 17:00 - 2010-11-23 17:30 -> 20 chairs reserved

A user makes several reservation requests (queries):

Q1 2010-11-23 15:00 - 2010-11-23 17:00 -> 25 chairs are available
Q2 2010-11-23 15:45 - 2010-11-23 17:00 -> 30 chairs are available
Q3 2010-11-23 16:30 - 2010-11-23 18:00 -> 30 chairs are available
Q4 2010-11-23 15:10 - 2010-11-23 15:20 -> 25 chairs are available
Q5 2010-11-23 13:30 - 2010-11-23 17:30 -> 20 chairs are available

How would I query the maximum available quantity for a requested period? Or is a different table design needed? The target database systems are Oracle and SQL-Server.

Update:

I tried to "visualize" the reservations R1 and R2 and the queries Q1 - Q5 without changing the original examples. I added Q4 and Q5 as additional examples. av shows the available count.

       R1  R2  R3  av
13:30              40                  Q5
14:00   5          35                  Q5
14:30   5          35                  Q5
15:00   5  10      25  Q1              Q5
15:10   5  10      25  Q1          Q4  Q5
15:20   5  10      25  Q1              Q5
15:30      10      30  Q1              Q5
15:45      10      30  Q1  Q2          Q5
16:00              40  Q1  Q2          Q5
16:30              40  Q1  Q2  Q3      Q5
17:00          20  20          Q3      Q5
av                     25  30  20  25  20
+2  A: 

You could try something like this (full working example)

DECLARE @inventory TABLE(
    inventory_id int, 
    quantity int
)

DECLARE @reservation TABLE(
    reservation_id int,
    inventory_id int,
    quantity int,
    [from] datetime,
    until datetime
)

INSERT INTO @inventory SELECT 1, 40

INSERT INTO @reservation SELECT 1, 1, 5, '2010-11-23 14:00 ', '2010-11-23 15:30'
INSERT INTO @reservation SELECT 1, 1, 10, '2010-11-23 15:00 ', '2010-11-23 16:00'

DECLARE @Start DATETIME,
        @End DATETIME

SELECT  @Start = '2010-11-23 15:00',
        @End = '2010-11-23 17:00'

SELECT  TotalUsed.inventory_id,
        i.quantity - ISNULL(TotalUsed.TotalUsed,0) Available
FROM    @inventory i LEFT JOIN
        (
            SELECT  inventory_id,
                    SUM(quantity) TotalUsed
            FROM    @reservation
            WHERE   [from] BETWEEN @Start AND @End
            OR      until BETWEEN @Start AND @End
            GROUP BY inventory_id
        ) TotalUsed ON  TotalUsed.inventory_id = i.inventory_id


SELECT  @Start = '2010-11-23 15:45',
        @End = '2010-11-23 17:00'

SELECT  TotalUsed.inventory_id,
        i.quantity - ISNULL(TotalUsed.TotalUsed,0) Available
FROM    @inventory i LEFT JOIN
        (
            SELECT  inventory_id,
                    SUM(quantity) TotalUsed
            FROM    @reservation
            WHERE   [from] BETWEEN @Start AND @End
            OR      until BETWEEN @Start AND @End
            GROUP BY inventory_id
        ) TotalUsed ON  TotalUsed.inventory_id = i.inventory_id

Results

inventory_id Available
------------ -----------
1            25


inventory_id Available
------------ -----------
1            30
astander
Doesn't cover situations where a reservation has `[from]` before `@Start` and `until` after `@end` - eg. if `[from]` was `'2010-11-22 09:00'` and `until` was `'2010-11-24 18:00'`.
Mark Bannister
I have updated the example. I think this gets the wrong result for Q4 (15:10-15:20). It returns 40.
Soundlink
+1  A: 

Using SQLServer syntax:

SELECT i.inventory_id,
       MAX(i.quantity) - COALESCE(SUM(r.quantity), 0) AS available            
FROM INVENTORY i     
LEFT JOIN RESERVATIONS r 
ON (r.inventory_id = i.inventory_id AND
    r.[from] <= @End AND
    r.until >= @Start)           
GROUP BY i.inventory_id

I assume the supplied structures are a simplified version of the actual structures being used - if not, I recommend against using keywords such as FROM as column names.

EDIT: new query, assuming bookings are only to the nearest minute and never more than one week long:

with number_cte(n, n2) as 
 (select n, n+1 n2 from (select 0 n) m union all select n+1 n, n2+1 n2 
  from number_cte where n < datediff("mi",@start,@end))
SELECT i.inventory_id, max(i.quantity) - COALESCE(max(a.alloc), 0) AS available 
from INVENTORY as i  
join
(select n.datesel, r.inventory_id, sum(r.quantity) alloc from
 (select dateadd("mi",n,@Start) datesel from number_cte) as n  
 JOIN RESERVATIONS r 
 ON n.datesel between r.[from] AND r.until 
 GROUP BY n.datesel, r.inventory_id) a 
on i.inventory_id = a.inventory_id
GROUP BY i.inventory_id option (maxrecursion 10080)

This would actually be easier in Oracle, as you could use a connect by level rather than a CTE - if you are going to have reservations longer than one week, you will need to increase the maxrecursion number accordingly.

Mark Bannister
Yes it's only simplified. from is not the real column name. It's a bad name. I noticed it after the first answer. I think your query gets the right results.
Soundlink
I suspected, that it can't be so easy. I have updated the question and added the reservation R3. If there is a query Q5 (13:30-17:30) this query sums all reservations leaving 5 available, but the result should be 20, because this is the maximum number which is needed at the same time (-slot).
Soundlink
@soundlink - try the new query.
Mark Bannister