问题:给定一系列可以为空的开始日期和结束日期,优化以下查询的最佳方法是(底部的示例模式):
-- 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);
商业条件有效:
下面显示 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);
答案 0 :(得分:3)
我的猜测是最好的方法如下。首先,在start
和end
上创建索引:
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将根据查询中的“常量”日期选择任一索引。