3

I have been struggling a bit with a query and I'm not sure if its possible in pure sql or needs to be backed by php.

Basically I want to generate an order report listing the products that have been overbooked in the system.

Table structure:

orders

id int
bookingStart DATETIME
bookingEnd DATETIME

order_lines

id int
qty int
product_id int
booking_id int

products

id int
stock int

An order can contain multiple lines with the same product. So there needs to be a SUM for each product on an order. Does anyone have any suggestions for the best way to accomplish this?

I have tried looping in a specific time interval with a subquery, but it does not take into account the overlap between different bookings and im kinda stuck here, if it can be done with a single query:

SELECT  (SELECT SUM(lin2.qty) FROM booking_lines lin2,orders b2
                       WHERE
                         lin2.product_id=e.id    AND
                       b2.id=lin2.booking_id  AND                           (
                       (b2.bookingStart BETWEEN b1.bookingStart AND b1.bookingEnd) OR
                       (b2.bookingEnd BETWEEN b1.bookingStart AND b1.bookingEnd) OR
                       (b2.bookingStart < b1.bookingStart AND b2.bookingEnd >     b1.bookingEnd)                           
                       )    as booked SUM(lin1.qty) 
                 FROM orders b1
                 LEFT JOIN order_lines lin1 ON b1.id=lin1.booking_id
                 LEFT JOIN products e ON e.id=lin1.product_id

                (
                    (b1.bookingStart BETWEEN '$s' AND '$e') OR
                    (b1.bookingEnd BETWEEN '$s' AND '$e') OR
                    (b1.bookingStart < '$s' AND b1.bookingEnd > '$e') 
        )
         GROUP BY b1.id,lin1.product_id

The problem If we have product x with a stock of 10.

Order 1 has booked 3 product x Order 2 has booked 5 product x Order 3 has booked 8 product x

The lines below indicate the booked timespan

______Order 1 x:3____
       ___Order 2 x:5_____
__________Order 3 x:8______
                                            __order 4 x:2_

So its only when the 3 orders overlap that product x is actually overbooked, what i cant figure out is how to create a query capable of detecting that.

sbdd
  • 31
  • 2
  • 4
    Welcome to StackOverflow! Please post sample data, desired output based on it, and what you've tried so far so we can see your effort. – peterm Jun 11 '13 at 19:26
  • In your query you have a `LEFT JOIN` to a table called `booking_lines`. Did you mean `order_lines` or it's a totally different table? – tftd Jun 11 '13 at 21:14
  • Based on your comment to my answer below, I think you need to specify the granularity of the time-frame in order to figure out what the overlap is. Days?, Hours? – Sako73 Jun 13 '13 at 15:16

2 Answers2

1

I do not have an instance of MySql to test with, but have you tried something like this:

select p.id, p.stock, t.bookedQty
from products as p
    inner join (
            select ol.product_id, sum(ol.qty) as bookedQty
            from order_lines as ol
                inner join orders as o on ol.booking_id = o.id
            where o.bookingStart between '2013-06-10' and '2013-06-11'
                    and t.bookedQty > p.stock
            group by ol.product_id) as t

--See comment 1--

I used the SqlFiddle schema from tftd's answer below (thanks). This should generate every day for the next 100 that has an overbooking, the quantity booked, and the quantity in stock:

SELECT
        date,
        p.product_id,
        sum(ol.qty) AS total_booked,
        p.stock AS available
FROM  (
        SELECT c.date
        FROM
          (SELECT curdate() + interval (a.a + (10 * b.a)) DAY AS date
           FROM
             (SELECT 0 AS a
              UNION ALL SELECT 1
              UNION ALL SELECT 2
              UNION ALL SELECT 3
              UNION ALL SELECT 4
              UNION ALL SELECT 5
              UNION ALL SELECT 6
              UNION ALL SELECT 7
              UNION ALL SELECT 8
              UNION ALL SELECT 9) AS a CROSS
           JOIN
             (SELECT 0 AS a
              UNION ALL SELECT 1
              UNION ALL SELECT 2
              UNION ALL SELECT 3
              UNION ALL SELECT 4
              UNION ALL SELECT 5
              UNION ALL SELECT 6
              UNION ALL SELECT 7
              UNION ALL SELECT 8
              UNION ALL SELECT 9) AS b
          ) AS c
        WHERE c.date >= curdate() AND c.date < DATE_ADD(curdate(), INTERVAL 100 DAY)
    ) AS gendates
    INNER JOIN orders AS o ON o.bookingStart <= gendates.date AND o.bookingEnd > gendates.date
    INNER JOIN order_lines AS ol ON o.order_id = ol.order_id
    INNER JOIN products AS p ON ol.product_id = p.product_id
GROUP BY gendates.date, p.product_id, p.stock
HAVING total_booked > p.stock

http://sqlfiddle.com/#!2/a5061e/25

Date generation link

Community
  • 1
  • 1
Sako73
  • 9,957
  • 13
  • 57
  • 75
  • I tried something along those lines but it dosent take overlaps into account. I have updated the question with more details. – sbdd Jun 12 '13 at 09:00
  • It is very close to what i want except that bookingStart and bookingEnd is a DATE TIME so i need to find the specific interval where the overbooking occurs. – sbdd Jun 14 '13 at 12:28
1

Table structure

CREATE TABLE products (
    product_id INT AUTO_INCREMENT PRIMARY KEY,
    name       VARCHAR(255),
    stock      INT
) ENGINE=InnoDB;

CREATE TABLE orders (
    order_id     INT AUTO_INCREMENT PRIMARY KEY,
    bookingStart DATETIME,
    bookingEnd   DATETIME
) ENGINE=InnoDB;


CREATE TABLE order_lines (
    line_id       INT AUTO_INCREMENT PRIMARY KEY,
    order_id      INT,
    product_id    INT,
    qty           INT NOT NULL DEFAULT 0,
    CONSTRAINT FOREIGN KEY(order_id)   REFERENCES orders(order_id) ON DELETE RESTRICT,
    CONSTRAINT FOREIGN KEY(product_id) REFERENCES products(product_id) ON DELETE RESTRICT
) ENGINE=InnoDB;


INSERT INTO products(name, stock) VALUES('Product 1', 8);
INSERT INTO products(name, stock) VALUES('Product 2', 14);
INSERT INTO products(name, stock) VALUES('Product 3', 25);

INSERT INTO orders(bookingStart,bookingEnd) VALUES(NOW(), (NOW() + INTERVAL 2 HOUR));
INSERT INTO orders(bookingStart,bookingEnd) VALUES(NOW(), (NOW() + INTERVAL 3 HOUR));
INSERT INTO orders(bookingStart,bookingEnd) VALUES(NOW(), (NOW() + INTERVAL 4 HOUR));

INSERT INTO order_lines(order_id, product_id, qty) VALUES(1, 1, 2);
INSERT INTO order_lines(order_id, product_id, qty) VALUES(1, 1, 8);
INSERT INTO order_lines(order_id, product_id, qty) VALUES(2, 2, 5);
INSERT INTO order_lines(order_id, product_id, qty) VALUES(2, 2, 10);
INSERT INTO order_lines(order_id, product_id, qty) VALUES(3, 3, 2);
INSERT INTO order_lines(order_id, product_id, qty) VALUES(3, 3, 8);
INSERT INTO order_lines(order_id, product_id, qty) VALUES(3, 3, 10);

To get the overbooked products:

SELECT
  products.*,
  SUM(order_lines.qty) as sum_qty
FROM products
LEFT JOIN order_lines ON order_lines.product_id = products.product_id
LEFT JOIN orders ON orders.order_id = order_lines.order_id
WHERE orders.bookingStart >= '2013-06-12' 
AND orders.bookingEnd <= '2013-06-14'
GROUP BY order_lines.order_id, order_lines.product_id
HAVING sum_qty > products.stock

Will return Product 1 and Product 2, because the sum of qty in order_lines is bigger than products.stock (which is probably the available quantity of the product?).

SqlFiddle

tftd
  • 16,203
  • 11
  • 62
  • 106
  • Yeah but it needs to take the time interval into account because its a rental product. So its only if its overbooked in a specific time interval, the problem is there can be multiple orders spanning the same time interval so it needs to find the maximum qty, taking the time interval into account. – sbdd Jun 12 '13 at 08:25
  • Well just add a `WHERE` clause before `GROUP BY` in the query and it will filter them. I did not add it because I had just 3 records. – tftd Jun 12 '13 at 08:30
  • @sdbb as I said in my previous comment, just add a `WHERE` clause to the query. You can try it here http://sqlfiddle.com/#!2/117773/4 to see the result. This is what you want, right? – tftd Jun 12 '13 at 09:14
  • Except i want to execute the query and then it should be able to find the maximum qty booked at any given time where it is greater than the stock, not on a single order. For instance according to the example in the question order 1,2,3 overlap giving a total qty of 16, order 3,4 overlap giving a total qty 10 – sbdd Jun 12 '13 at 11:33
  • So, you want to get all orders between `DATE1` and `DATE2` which hold products over the limit (`product.stock`), is that right ? – tftd Jun 12 '13 at 16:18
  • I want to find the combined totals not just on a single order e.g. as above. If two or more orders combined have booked more than stock in any given time interval. – sbdd Jun 12 '13 at 17:24