12

背景/アプリケーション

賃貸可能なプロパティのテーブルとこれらのプロパティの予約のテーブルを含むMySQLデータベースがあります。提供された2つの日付の間に利用可能なプロパティを見つけるための検索機能もあります。検索時に、ユーザーは開始日、滞在したい日数、および最大+/-7日の日付の柔軟性を入力できます。予約は、別の予約が終了するのと同じ日に開始できます(パーティー1は朝に出発し、パーティー2は夕方に到着します)。

柔軟性機能を効率的に実装するのに苦労しています。

スキーマ

CREATE TABLE IF NOT EXISTS `property` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `name` varchar(60) COLLATE utf8_unicode_ci DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE IF NOT EXISTS `property_booking` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `property_id` bigint(20) DEFAULT NULL,
    `name` varchar(60) COLLATE utf8_unicode_ci DEFAULT NULL,
    `date_start` date DEFAULT NULL,
    `date_end` date DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

サンプルデータ

INSERT INTO `property` (`name`) 
VALUES ('Property 1'), ('Property 2'), ('Property 3');

INSERT INTO `property_booking` (`property_id`,`name`,`date_start`,`date_end`) 
VALUES (1, 'Steve', '2011-03-01', '2011-03-08'), 
(2, 'Bob', '2011-03-13', '2011-03-20'), 
(3, 'Jim', '2011-03-16', '2011-03-23');

サンプルシナリオ

ユーザーは、2011年3月10日に滞在を開始し、7日間滞在し、+/-2日間の柔軟性があることを選択します。以下のデータとパラメータを視覚化した画像を編集しました。(赤:予約1、緑:予約2、ストライプ:予約3、青:日付範囲(2011-03-10、+7日および+/-2日の柔軟性))

期待される結果

プロパティ1 (日付範囲全体で利用可能な予約)
プロパティ3 (2011-03-08または2011-03-09から利用可能な予約)

現在の方法

私の現在のクエリは、次のように、検索可能な合計日付範囲内の7日間の日付範囲すべての重複をチェックします。

SELECT p.`id`, p.`name` 
FROM `property` p 
WHERE (NOT (EXISTS (SELECT p2.`name` FROM `property_booking` p2 WHERE (p2.`property_id` = p.`id` AND '2011-03-10' < DATE_SUB(p2.`date_end`, INTERVAL 1 DAY) AND '2011-03-17' > DATE_ADD(p2.`date_start`, INTERVAL 1 DAY))))) 
OR (NOT (EXISTS (SELECT p3.`name` FROM `property_booking` p3 WHERE (p3.`property_id` = p.`id` AND '2011-03-11' < DATE_SUB(p3.`date_end`, INTERVAL 1 DAY) AND '2011-03-18' > DATE_ADD(p3.`date_start`, INTERVAL 1 DAY))))) 
OR (NOT (EXISTS (SELECT p4.`name` FROM `property_booking` p4 WHERE (p4.`property_id` = p.`id` AND '2011-03-09' < DATE_SUB(p4.`date_end`, INTERVAL 1 DAY) AND '2011-03-16' > DATE_ADD(p4.`date_start`, INTERVAL 1 DAY))))) 
OR (NOT (EXISTS (SELECT p5.`name` FROM `property_booking` p5 WHERE (p5.`property_id` = p.`id` AND '2011-03-12' < DATE_SUB(p5.`date_end`, INTERVAL 1 DAY) AND '2011-03-19' > DATE_ADD(p5.`date_start`, INTERVAL 1 DAY)))))
OR (NOT (EXISTS (SELECT p6.`name` FROM `property_booking` p6 WHERE (p6.`property_id` = p.`id` AND '2011-03-08' < DATE_SUB(p6.`date_end`, INTERVAL 1 DAY) AND '2011-03-15' > DATE_ADD(p6.`date_start`, INTERVAL 1 DAY)))));

サンプルデータセットでは、かなり高速ですが、はるかに大きなデータセットでは、かなり遅くなります。完全な+/-7日間の柔軟性を構築するとさらに遅くなります。

このクエリをより適切に作成する方法について誰かが何か提案がありますか?

4

2 に答える 2

2

わかりました、これがトリッキーな質問に対するトリッキーな答えです...

SELECT * FROM property AS p
LEFT JOIN  
(
  SELECT property_id, DATEDIFF(MAX(date_end),20110308) AS startblock, 
      DATEDIFF(20110319,MIN(date_start))-1 AS endblock
  FROM property_booking AS pb
  WHERE date_start < 20110319 || date_end >= 20110308 
  GROUP BY property_id
  HAVING LEAST(startblock,endblock) > 4
) AS p2 ON p.id = p2.property_id 
WHERE p2.property_id IS NULL;

サブクエリは、適格ではないすべてのプロパティを選択します。ISNULLを使用したLEFTJOINは、基本的に除外を解決します(不適格なプロパティの否定)

  • 20110308は、希望する開始日-2日です(+/- 2日の柔軟性があるため)
  • 20110319は希望する終了日+2日です
  • +/-数の2倍の数4 HAVING LEAST(startblock,endblock) > 4(2 * 2)

それを解決するのに少し時間がかかりました(しかし、あなたの質問は面白くて、私は私の手に時間がありました)

私はそれをエッジケースでテストしました、そしてそれは私がそれに投げたすべてのテストケースで機能しました...)。その背後にある論理は少し奇妙ですが、古き良きペンと紙が私がそれを解決するのを助けました!

編集

残念ながら、これはほとんどの場合に機能するが、すべてではないことに気づきました...(ルックアップ期間の最初と最後に2日間の予約を行うと、プロパティが利用可能であるはずなのに利用できなくなります)。

ここでの問題は、DBに「存在しない」情報を検索し、所有しているデータから再構築する必要があることです。問題に対処するためのより良い方法を確認するためにあなたの質問に対する私のコメントをチェックしてください

于 2011-02-17T22:20:09.273 に答える
0

私はこれがあなたが探しているものだと思います:

   SELECT MAX( IF( (    b.date_start < '2011-03-08' + INTERVAL 7 DAY
                    AND b.date_end > '2011-03-08'), 1, 0)) AS is_booked,
          p.id,
          p.name
     FROM property p
LEFT JOIN property_booking b ON p.id = b.property_id
 GROUP BY p.id
   HAVING is_booked < 1

余裕を持たせたい場合は、MAX()集計を展開してオプションを含めます。

   SELECT MAX( IF(    (    b.date_start < '2011-03-08' + INTERVAL 7 DAY
                       AND b.date_end > '2011-03-08')
                  AND (    b.date_start < '2011-03-08' + INTERVAL 7 DAY + INTERVAL 1 DAY
                       AND b.date_end > '2011-03-08' + INTERVAL 1 DAY)
                  AND (    b.date_start < '2011-03-08' + INTERVAL 7 DAY + INTERVAL 2 DAY
                       AND b.date_end > '2011-03-08' + INTERVAL 2 DAY), 1, 0)
             ) AS is_booked,
          p.id,
          p.name
     FROM property p
LEFT JOIN property_booking b ON p.id = b.property_id
 GROUP BY p.id
   HAVING is_booked < 1

私があなたの質問を正しく理解しているのであれば、このGROUP BYクエリは、複数のサブクエリよりも効率的にそれをカバーするはずです。

于 2011-02-17T21:41:07.313 に答える