使用MySQL(和PHP)搜索可用性?

时间:2009-11-06 13:13:52

标签: php mysql

我有两个MySQL(MyIsAm)表,代表租赁单位和预订:

  • LettingUnits(IDName等等......)
  • LettingUnitBookings(IDF_LU_IDStartEnd

F_LU_ID是单位的外键。

搜索特定时间段内可用单位的最佳方法是什么?搜索传递开始,结束和持续时间。

  • 开始=最早开始预订
  • 结束=预订的最新结束
  • 持续时间=预订的持续时间

我有兴趣知道是否甚至可以在MySQL中执行此操作,但如果不是,那么最好的方法是在PHP中执行此操作。

示例

在回答下面的答案时,我觉得一个例子可以帮助解释这个问题。

一个LettingUnit:

  • (123,“Foo Cottage”)

一些LettingUnitBookings:

  • (400,123,01 / 01 / 09,05 / 01/09) - 为期5天的预订
  • (401,123,10 / 01 / 09,20/01/09) - 10天预订
  • (402,123,25 / 01 / 09,30 / 01/09) - 为期5天的预订

如果我们搜索:

  • Start = 01/01/09
  • 结束= 01/02/09
  • 持续时间= 5(天)

然后我们希望单位出现。因为在搜索范围内可以预订5天。

如果持续时间为10,那么该单位将不会显示,因为搜索范围内没有10个连续的未预订天数。

2 个答案:

答案 0 :(得分:2)

这是一个似乎有效的解决方案:

SELECT t.*, DATEDIFF(t.LatestAvailable, t.EarliestAvailable) AS LengthAvailable
FROM 
   (SELECT u.*,
      COALESCE(b1.End, @StartOfWindow) AS EarliestAvailable,
      COALESCE(b2.Start, @EndOfWindow) AS LatestAvailable
    FROM LettingUnits u
    LEFT OUTER JOIN LettingUnitBookings b1
      ON (u.ID = b1.F_LU_ID AND b1.End BETWEEN @StartOfWindow AND @EndOfWindow)
    LEFT OUTER JOIN LettingUnitBookings b2
      ON (u.ID = b2.F_LU_ID AND b2.Start BETWEEN @StartOfWindow AND @EndOfWindow
          AND b2.Start >= b1.End) -- edit: new term
    ) AS t
LEFT OUTER JOIN LettingUnitBookings x
  ON (t.ID = x.F_LU_ID AND x.Start < t.LatestAvailable AND x.End > t.EarliestAvailable)
WHERE x.ID IS NULL AND DATEDIFF(t.LatestAvailable, t.EarliestAvailable) >= @WindowSize;

输出结果为:

+-----+-------------+-------------------+-----------------+-----------------+
| ID  | Name        | EarliestAvailable | LatestAvailable | LengthAvailable |
+-----+-------------+-------------------+-----------------+-----------------+
| 123 | Foo Cottage | 2009-01-05        | 2009-01-10      |               5 |
| 123 | Foo Cottage | 2009-01-20        | 2009-01-25      |               5 |
| 456 | Bar Cottage | 2009-01-20        | 2009-01-31      |              11 |
+-----+-------------+-------------------+-----------------+-----------------+

使用EXPLAIN进行分析表明它很好地使用了索引:

+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------------------+
| id | select_type | table      | type   | possible_keys | key     | key_len | ref   | rows | Extra                   |
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------------------+
|  1 | PRIMARY     | <derived2> | ALL    | NULL          | NULL    | NULL    | NULL  |    9 | Using where             |
|  1 | PRIMARY     | x          | ref    | F_LU_ID       | F_LU_ID | 8       | t.ID  |    2 | Using where; Not exists |
|  2 | DERIVED     | u          | system | NULL          | NULL    | NULL    | NULL  |    1 |                         |
|  2 | DERIVED     | b1         | ref    | F_LU_ID       | F_LU_ID | 8       | const |    0 |                         |
|  2 | DERIVED     | b2         | ref    | F_LU_ID       | F_LU_ID | 8       | const |    0 |                         |
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------------------+

与@martin clayton的解决方案givenEXPLAIN报告进行比较:

+----+--------------+---------------------+--------+---------------+---------+---------+------+------+---------------------------------+
| id | select_type  | table               | type   | possible_keys | key     | key_len | ref  | rows | Extra                           |
+----+--------------+---------------------+--------+---------------+---------+---------+------+------+---------------------------------+
|  1 | PRIMARY      | lu                  | system | PRIMARY,ID    | NULL    | NULL    | NULL |    1 |                                 |
|  1 | PRIMARY      | <derived2>          | ALL    | NULL          | NULL    | NULL    | NULL |    4 | Using where                     |
|  2 | DERIVED      | <derived3>          | ALL    | NULL          | NULL    | NULL    | NULL |    4 | Using temporary; Using filesort |
|  2 | DERIVED      | <derived5>          | ALL    | NULL          | NULL    | NULL    | NULL |    4 | Using where; Using join buffer  |
|  5 | DERIVED      | LettingUnitBookings | ALL    | NULL          | NULL    | NULL    | NULL |    3 |                                 |
|  6 | UNION        | LettingUnitBookings | index  | NULL          | F_LU_ID | 8       | NULL |    3 | Using index                     |
| NULL | UNION RESULT | <union5,6>          | ALL    | NULL          | NULL    | NULL    | NULL | NULL |                                 |
|  3 | DERIVED      | LettingUnitBookings | ALL    | NULL          | NULL    | NULL    | NULL |    3 |                                 |
|  4 | UNION        | LettingUnitBookings | index  | NULL          | F_LU_ID | 8       | NULL |    3 | Using index                     |
| NULL | UNION RESULT | <union3,4>          | ALL    | NULL          | NULL    | NULL    | NULL | NULL |                                 |
+----+--------------+---------------------+--------+---------------+---------+---------+------+------+---------------------------------+

通常,您希望避免强制Using filesortUsing temporary的优化计划,因为这些是性能杀手。使用GROUP BY的查询几乎肯定会导致这种优化,至少在MySQL中。

答案 1 :(得分:1)

它不漂亮。

  • 将LettingUnitBookings加入自身
  • 查找每个F_LU_ID
  • 预订之间的差距的开始和结束
  • 获取间隙的大小 - 可用的'插槽'
  • 考虑这样一种情况,即现有的预订没有“包含”合适的广告位,请为此添加离群值日期
  • 将该投影加入LettingUnits表并应用WHERE条件(开始,结束,持续时间)

我忽略了包含没有预订的BookingUnits。

看起来像这样:

SELECT @StartOfWindow := '2009-01-01',
       @EndOfWindow   := '2009-02-01',
       @WindowSize    := 5
;

SELECT
    lu.Name,
    Slots.*
FROM (
    SELECT
        lub1.F_LU_ID,
        DATE_ADD( MAX( lub2.date_time ), INTERVAL 1 DAY )     AS StartOfSlot,
        DATE_SUB( lub1.date_time, INTERVAL 1 DAY )            AS EndOfSlot,
        DATEDIFF( lub1.date_time, MAX( lub2.date_time ) ) - 1 AS AvailableDays
    FROM
    ( SELECT F_LU_ID, Start AS date_time FROM LettingUnitBookings
      UNION
      SELECT F_LU_ID, CAST( '9999-12-31' AS DATE ) FROM LettingUnitBookings
    ) AS lub1,
    ( SELECT F_LU_ID, End   AS date_time FROM LettingUnitBookings
      UNION
      SELECT F_LU_ID, CAST( '1000-01-01' AS DATE ) FROM LettingUnitBookings
    ) AS lub2
    WHERE
        lub2.date_time <= lub1.date_time
    AND lub2.F_LU_ID = lub1.F_LU_ID
    GROUP BY
        lub1.F_LU_ID,
        lub1.date_time
    ) Slots
JOIN LettingUnits lu
ON   lu.ID = Slots.F_LU_ID
WHERE
    Slots.AvailableDays >= @WindowSize
AND (
   (     DATEDIFF( Slots.EndOfSlot, @EndOfWindow )     >= @WindowSize
     AND DATEDIFF( @StartOfWindow, Slots.StartOfSlot ) >= @WindowSize
   )
   OR
   (     DATEDIFF( @EndOfWindow, Slots.StartOfSlot ) >= @WindowSize
     AND DATEDIFF( Slots.EndOfSlot, @StartOfWindow ) >= @WindowSize
   )
)

给出

Name        F_LU_ID StartOfSlot EndOfSlot  AvailableDays
Foo Cottage 123     2009-01-06  2009-01-09 5
Foo Cottage 123     2009-01-21  2009-01-24 5

希望可以根据您的需要进行调整。

或者,如果预订可以在上次预订结束的同一天开始,您可以稍微调整一下......

SELECT
    lu.Name,
    Slots.*
FROM (
    SELECT
        lub1.F_LU_ID,
        MAX( lub2.date_time ) AS StartOfSlot,
        lub1.date_time        AS EndOfSlot,
        DATEDIFF( lub1.date_time, MAX( lub2.date_time )) AS AvailableDays
    FROM
    ( SELECT F_LU_ID, Start AS date_time FROM LettingUnitBookings
      UNION
      SELECT F_LU_ID, CAST( '9999-12-31' AS DATE ) FROM LettingUnitBookings
    ) AS lub1,
    ( SELECT F_LU_ID, End   AS date_time FROM LettingUnitBookings
      UNION
      SELECT F_LU_ID, CAST( '1000-01-01' AS DATE ) FROM LettingUnitBookings
    ) AS lub2
    WHERE
        lub2.date_time <= lub1.date_time
    AND lub2.F_LU_ID = lub1.F_LU_ID
    GROUP BY
        lub1.F_LU_ID,
        lub1.date_time
    ) Slots
JOIN LettingUnits lu
ON   lu.ID = Slots.F_LU_ID
WHERE
    Slots.AvailableDays >= @WindowSize
AND
   (     DATEDIFF( Slots.EndOfSlot, @EndOfWindow )     >= @WindowSize
     AND DATEDIFF( @StartOfWindow, Slots.StartOfSlot ) >= @WindowSize
   )
   OR
   (     DATEDIFF( @EndOfWindow, Slots.StartOfSlot ) >= @WindowSize
     AND DATEDIFF( Slots.EndOfSlot, @StartOfWindow ) >= @WindowSize
   )