如何优化搜索可以为空的开始日期或结束日期的SQL查询?

时间:2014-11-14 16:47:04

标签: mysql sql date datetime

问题:给定一系列可以为空的开始日期和结束日期,优化以下查询的最佳方法是(底部的示例模式)

-- Query I am trying to optimize
SELECT * FROM dateranges WHERE
    ('2014-11-10 05:59:59' > `start` AND '2014-11-03 06:00:00' <= `end`)
    OR ('2014-11-03 06:00:00' >= `start` AND `end` is null)
    OR ('2014-11-10 05:59:59' <= `end` AND `start` is null);

-- Same query but with placeholders for clarification
SELECT * FROM dateranges WHERE
    ('{endSearch}' > `start` AND '{startSearch}' <= `end`)
    OR ('{startSearch}' >= `start` AND `end` is null)
    OR ('{endSearch}' <= `end` AND `start` is null);

商业条件有效

  1. name.start &gt; = startSearch AND&lt; = endSearch
  2. name.start &gt; = startSearch AND name.end 是否为空?
  3. name.end &lt; = endSearch AND name.start 是否为空?

  4. 下面显示 EXPLAIN 只搜索开始和结束:

    1, SIMPLE, s, range, date_start_idx,date_end_idx, date_end_idx, 6, , 251, Using index condition; Using where; Using temporary; Using filesort
    

    下面显示添加了空搜索的 EXPLAIN

    1, SIMPLE, s, ALL, date_start_idx,date_end_idx, , , , 6340, Using where; Using temporary; Using filesort
    

    示例MySQL架构

    CREATE TABLE `dateranges` (
    `name` VARCHAR(45) NULL,
    `start` DATETIME NULL,
    `end` DATETIME NULL);
    
    INSERT INTO `dateranges` (`name`,`start`,`end`) VALUES
    ('God',null,null),
    ('Dog',null,'2014-10-06'),
    ('Cat','2014-10-01',null),
    ('People','2014-10-02','2014-10-04');
    
    ALTER TABLE `dateranges` 
    ADD INDEX `index1` (`start` ASC),
    ADD INDEX `index2` (`end` ASC);
    

2 个答案:

答案 0 :(得分:3)

我的猜测是最好的方法如下。首先,在startend上创建索引:

create index idx_dateranges_start_end on dateranges(start, end)

然后使用union all修改查询:

SELECT * FROM dateranges WHERE ('2014-11-10 05:59:59' > `start` AND
                                '2014-11-03 06:00:00' <= `end` AND
                                '2014-11-03 06:00:00' < `start`
                               )
UNION ALL
SELECT * FROM dateranges WHERE ('2014-11-03 06:00:00' >= `start` AND `end` is null)
UNION ALL
SELECT * FROM dateranges WHERE ('2014-11-10 05:59:59' <= `end` AND `start` is null);

MySQL应该对三个where条件进行索引查找。它们是互斥的,因此union all不会产生任何重复。 MySQL(以及其他SQL引擎)使用or优化条件表达式的工作非常糟糕。

编辑:

另一种方法是插入假日期,你可以NULL,开始日期为'2000-01-01',结束日期为'2100-12-31'。然后你可以免除附加条款:

SELECT *
FROM dateranges
WHERE ('2014-11-10 05:59:59' > `start` AND '2014-11-03 06:00:00' <= `end`)

答案 1 :(得分:2)

我将假设您要选择与日期范围重叠的所有行 [2014-11-03 06:00:00,2014-11-10 06:00:00] ,结束日期是独家的。选择此类记录的查询是:

SELECT * FROM dateranges
WHERE '2014-11-10 06:00:00' > `start`
AND `end` > '2014-11-03 06:00:00'

为了处理NULL日期,我建议您将日期列设为NOT NULL并存储不切实际的值(例如1000-01-01表示开始而9999-12-31表示结束)而不是NULL。


话虽如此,您可以使用OR子句检查NULL,并仍然使用此查询使用索引:

SELECT a.*
FROM dateranges AS a
INNER JOIN dateranges AS b ON a.id = b.id
WHERE ('2014-11-10 06:00:00' > a.`start` OR a.`start` IS NULL)
AND (b.`end` > '2014-11-03 06:00:00' OR b.`end` IS NULL)

此查询可以使用两个索引,因为AND-OR子句是策略性分组的。 HOWEVER ,如果MySQL由于某种原因选择不使用索引(例如由于基数不足),那么此查询将比无连接版本执行得更糟。


同时在开始日期和结束日期创建索引。如果在(开始,结束)上创建复合索引,那么您还应该创建(结束,开始)索引。 MySQL将根据查询中的“常量”日期选择任一索引。