列出连续记录范围的有效方法

Efficient way to list ranges of consecutive records

我有一个 table 设置如下:

CREATE TABLE `cn` (
    `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
    `type` int(3) unsigned NOT NULL,
    `number` int(10) NOT NULL,
    `desc` varchar(64) NOT NULL,
    `datetime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB

number 通常但 不一定 是唯一的。

大部分 table 由具有连续 number 个条目的行组成。

例如

101010、101011、101012 等

我一直在努力寻找一种 高效 的方法来列出连续数字的范围,以便我可以轻松地找出数字 "missing" 的位置。我想做的是列出开始编号、结束编号和连续行数。由于可能存在重复,我使用 SELECT DISTINCT(number) 来避免重复。

我运气不太好 - 大多数这类问题都与日期有关,很难一概而论。一个查询永远在执行,所以这是不行的。 有点接近但不完全是。它使用 CROSS JOIN,当您拥有数百万条记录时,这听起来像是一场灾难。

最好的方法是什么?一些答案使用连接,我对性能明智持怀疑态度。现在只有 50,000 行,但几天之内就会有数百万条记录,因此每一盎司的性能都很重要。

我想到的最终伪查询是这样的:

SELECT DISTINCT(number) FROM cn WHERE type = 1 GROUP BY [consecutive...] ORDER BY number ASC

这是一个缺口和孤岛问题。你可以通过row_number()number之间的差异来定义组来解决;差距由差异的变化确定:

select type, min(number) first_number, max(number) last_number, count(*) no_records
from (
    select cn.*, row_number() over(order by number) rn
    from cn
    where type = 1
) c
group by type, number - rn

注意:window 函数在 MySQL 8.0 和 MariaDB 10.3 及更高版本中可用。


在早期版本中,您可以使用会话变量模拟 row_number()

select type, min(number) first_number, max(number) last_number, count(*) no_records
from (
    select c.*, @rn := @rn + 1 rn
    from (select * from cn where type = 1 order by number) c
    cross join (select @rn := 0) r
) c
group by number - rn