简体   繁体   中英

mysql query - how to find items not within certain dates

I'm pretty useless at SQL it seems and I'm trying to figure out what is the correct query to use.

I have a table Items and a table Reservations. An item can have many reservations and reservations are made between two dates.

I'm trying to create a search query which will return all the Items which don't have reservations between two user inputted dates.

the SQL I have at the minute looks like:

SELECT `Item`.`id`, `Item`.`user_id`, `Item`.`name`, `Item`.`description`, `User`.`id`, `User`.`username`, `User`.`password`, `User`.`email` 
FROM `database`.`items` AS `Item` 
LEFT JOIN `database`.`reservations` AS `ReservationJoin` ON (`ReservationJoin`.`item_id` = `Item`.`id` AND `ReservationJoin`.`start` >= '2013-07-17' and `ReservationJoin`.`finnish` <= '2013-07-20') 
LEFT JOIN `database`.`users` AS `User` ON (`Item`.`user_id` = `User`.`id`) 
WHERE ((`Item`.`name` LIKE '%projector%') OR (`Item`.`description` LIKE '%projector%') 
AND `ReservationJoin`.`id` IS NULL 
LIMIT 10

I'm doing this in cakephp 2.3 so the code looks like:

$this->paginate = array(
      "conditions" => array(
        "or" => array(
          "Item.name LIKE" => '%'.$projector.'%',
          "Item.description LIKE" => '%'.$projector.'%',
        ),
        "ReservationJoin.id" => null,
      ),
      "joins" => array(
        array(
          "table" => "reservations",
          "alias" => "ReservationJoin",
          "type" => "LEFT",
          "conditions" => array(
            "ReservationJoin.item_id = Item.id",
            "ReservationJoin.checkin >= '{$start}' and ReservationJoin.checkout <= '{$finnish}'",
          )
        )
      ),
      "limit"=>10
    );
    $data = $this->paginate('Item');

This isn't working and I think it's to do with the join not excluding the reservations properly. But I've not been able to figure out what the correct mysql is. Can a kind soul tell me what I should be using?

thanks

If something has a reservation between two dates, then one of the following is true:

  1. It has a start date between the dates
  2. It has an end date between the dates
  3. It has a start date before the earlier date and an end date after the second one

The following query uses this logic in a having clause. The approach is to aggregate at the item level and ensure that the three above conditions are true:

SELECT i.`id`, i.`user_id`, i.`name`, i.`description`
FROM `database`.`items`i LEFT JOIN
     `database`.`reservations` r
     ON r.`item_id` = i.`id`
WHERE ((i.`name` LIKE '%projector%') OR (i.`description` LIKE '%projector%')
group by i.id
having max(start between '2013-07-17' and '2013-07-20') = 0 and
       max(finish between '2013-07-17' and '2013-07-20') = 0 and
       max(start < '2013-07-17' and finished > '2013-07-20') = 0
LIMIT 10;

Note that none matches are returned, because the conditions are treated as false when start and ned are NULL.

you can try this.

SELECT `Item`.`id`, `Item`.`user_id`, `Item`.`name`, `Item`.`description`, `User`.`id`, `User`.`username`, `User`.`password`, `User`.`email` 
FROM `database`.`items` AS `Item` 
LEFT JOIN `database`.`reservations` AS `ReservationJoin` ON (`ReservationJoin`.`item_id` = `Item`.`id`) 
LEFT JOIN `database`.`users` AS `User` ON (`Item`.`user_id` = `User`.`id`) 
WHERE ((`Item`.`name` LIKE '%projector%') OR (`Item`.`description` LIKE '%projector%')) 
AND `ReservationJoin`.`start` >= '2013-07-17' 
AND `ReservationJoin`.`finnish` <= '2013-07-20'
AND `ReservationJoin`.`id` IS NULL 
LIMIT 10

ie just move the date clauses into the WHERE clause as oppose to being in the JOIN CLAUSE

I think it may be easier for you in CakePHP to put all of the conditions in a WHERE clause. (This could also be done with some OUTER JOINs but it may be difficult to transcribe into CakePHP)

    SELECT id, user_id, name, description
    FROM items
    WHERE ((name LIKE '%projector%') OR (description LIKE '%projector%'))
    AND NOT EXISTS(
    SELECT *
    FROM reservations
    WHERE items.id = reservations.item_id
    AND (
    '2013-07-17' BETWEEN start and finnish
    OR
    '2013-07-20' BETWEEN start and finnish
    OR 
    start BETWEEN '2013-07-17' AND '2013-07-20'));

Or, using the same logic the WHERE clause can be cleaned up to be

    SELECT id, user_id, name, description
    FROM items
    WHERE ((name LIKE '%projector%') OR (description LIKE '%projector%'))
    AND NOT EXISTS(
    SELECT *
    FROM reservations
    WHERE items.id = reservations.item_id
    AND NOT(
    '2013-07-17' > finnish
    OR
    '2013-07-20' < start
    ));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM