MySQL嵌套SELECT BETWEEN多行查询报错:如何实现多事件日期范围取日历数据
原SQL报错的核心原因是BETWEEN语法要求两侧为单个可比较的值,但原写法中的两个子查询在存在多条eventweek事件时会返回多行结果,MySQL无法将多行结果直接作为单个值参与比较运算,因此抛出"subquery returns more than 1 row"错误。
同时原有逻辑本身也存在设计缺陷:即使子查询不报错,原写法也只会取单条事件的起止值,完全无法同时匹配多段独立的日期区间,比如示例中2013年7月和2014年7月的两段事件范围。
直接采用内连接(INNER JOIN)+ 区间匹配的写法,这是MySQL中处理这类多区间匹配的最高效方案,配合字段索引可以达到和单表查询接近的性能。该写法天然适配视图创建要求,不需要编写任何动态逻辑或循环代码,未来新增的eventweek事件会自动被关联匹配,无需修改查询逻辑。
前置性能优化(必做)
为相关字段建立索引,避免全表扫描带来的性能损耗:
-- 日历表日期字段建索引 CREATE INDEX idx_calendar_date ON calendar(`date`); -- 事件表建立标题、起止日期的联合索引 CREATE INDEX idx_events_title_range ON events(title, `date`, end_date);
视图创建SQL
执行以下语句创建可复用的视图,后续直接查询视图即可拿到所有匹配结果:
CREATE OR REPLACE VIEW v_eventweek_dates AS SELECT DISTINCT c.`date`, c.`day` FROM calendar c INNER JOIN events e -- 匹配规则:只要日历日期落在任意一条eventweek事件的起止区间内,就会被关联返回 ON c.`date` BETWEEN e.`date` AND e.end_date WHERE e.title LIKE '%eventweek%';
说明:添加
DISTINCT是为了规避重叠事件区间导致的同一日期重复返回的问题,保证每个日历日期只出现一次。
创建完成后,直接执行以下查询即可获取目标结果:
SELECT * FROM v_eventweek_dates;
可以自动匹配所有eventweek事件的区间范围,比如示例中2013-07-08至2013-07-14、2014-07-01至2014-07-13范围内的全部日历日期和对应星期。
如果特殊场景下无法使用JOIN关联,可以采用EXISTS子查询实现,在有索引的前提下性能和JOIN方案接近,且天然不会出现日期重复问题,不需要额外加DISTINCT去重,在事件区间重叠较多的场景下开销更低:
CREATE OR REPLACE VIEW v_eventweek_dates_exists AS SELECT c.`date`, c.`day` FROM calendar c WHERE EXISTS ( SELECT 1 FROM events e WHERE e.title LIKE '%eventweek%' AND c.`date` >= e.`date` AND c.`date` <= e.end_date );
内容的提问来源于stack exchange,提问作者El Mauro

