【问题标题】:How can I optimize this SQL query that searches nullable start or end dates?如何优化搜索可为空的开始或结束日期的 SQL 查询?
【发布时间】:2014-11-14 16:47:03
【问题描述】:

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

-- 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 >= startSearch AND endSearch 吗?
  2. name.start >= startSearch AND name.end 是否为空?
  3. name.end endSearch AND name.start 是否为空?

下面显示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);

【问题讨论】:

  • 在第一个条件中添加“start is not null”和“end is not null”将有助于消除所有空值。
  • 这个查询搜索什么?包含在指定日期内的记录还是与指定日期重叠/冲突的记录?
  • @SalmanA 查询返回用于内容搜索的名称 ID。有些名称可以有内容的开始时间,但没有结束时间。

标签: mysql sql date datetime


【解决方案1】:

我的猜测是最好的方法如下。首先,在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 是的,绝对是。也适用于 IN 子句。我自己对 SQL Server 上的 SELECT MIN(x) FROM WHERE someid IN (1,2) 有不好的体验。
  • 我会将其添加到原始问题中。开始和结束都有索引。但是查询仍然说它正在使用 where 子句。
  • @CGSmith105 。 . .您需要一个相对较大的表供 MySQL 使用索引。如果您的表只有几百——甚至几千——行,它可能会发现扫描速度更快。
  • @GordonLinoff MySQL 会切换是有道理的。你还会对索引使用联合吗?
【解决方案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 子句被策略性地分组。 但是,如果 MySQL 出于某种原因(例如,由于基数不足)选择不使用索引,则此查询的执行将比 no-join 版本差得多。


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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多