如何find序列号在MySQL中的差距?

我们有一个数据库,其中一个表的值是从另一个系统导入的。 有一个自动增量列,并没有重复的值,但缺less值。 例如,运行这个查询:

select count(id) from arrc_vouchers where id between 1 and 100 

应该返回100,但它返回87。 有没有我可以运行的任何查询将返回缺less的数字的值? 例如,logging可能存在id 1-70和83-100,但是没有logging的id为71-82。 我想要返回71,72,73等

这可能吗?

这是在任何大小的表(不只是在100行)工作的版本:

 SELECT (t1.id + 1) as gap_starts_at, (SELECT MIN(t3.id) -1 FROM arrc_vouchers t3 WHERE t3.id > t1.id) as gap_ends_at FROM arrc_vouchers t1 WHERE NOT EXISTS (SELECT t2.id FROM arrc_vouchers t2 WHERE t2.id = t1.id + 1) HAVING gap_ends_at IS NOT NULL 
  • gap_starts_at – 当前缺口中的第一个id
  • gap_ends_at – 当前缺口中的最后一个id

这只是为了find一个超过80k行的表格中的空白:

 SELECT CONCAT(z.expected, IF(z.got-1>z.expected, CONCAT(' thru ',z.got-1), '')) AS missing FROM ( SELECT @rownum:=@rownum+1 AS expected, IF(@rownum=YourCol, 0, @rownum:=YourCol) AS got FROM (SELECT @rownum:=0) AS a JOIN YourTable ORDER BY YourCol ) AS z WHERE z.got!=0; 

结果:

 +------------------+ | missing | +------------------+ | 1 thru 99 | | 666 thru 667 | | 50000 | | 66419 thru 66456 | +------------------+ 4 rows in set (0.06 sec) 

请注意,列的expectedgot的顺序是至关重要的。

如果你知道YourCol不是从1开始,那没关系,你可以replace

 (SELECT @rownum:=0) AS a 

 (SELECT @rownum:=(SELECT MIN(YourCol)-1 FROM YourTable)) AS a 

新的结果:

 +------------------+ | missing | +------------------+ | 666 thru 667 | | 50000 | | 66419 thru 66456 | +------------------+ 3 rows in set (0.06 sec) 

快速和肮脏的查询应该做的窍门:

 SELECT a AS id, b AS next_id, (b - a) -1 AS missing_inbetween FROM ( SELECT a1.id AS a , MIN(a2.id) AS b FROM arrc_vouchers AS a1 LEFT JOIN arrc_vouchers AS a2 ON a2.id > a1.id WHERE a1.id <= 100 GROUP BY a1.id ) AS tab WHERE b > a + 1 

这会给你一个表格,显示上面有id缺less的id,next_id存在,有多less缺less…

 
 id next_id missing_inbetween
  1 4 2
 68 70 1
 75 87 11

创build一个临时表,其中包含100行和包含值1-100的单个列。

外部将此表连接到您的arrc_vouchers表,并selectarrc_vouchers id为null的单列值。

编码这个盲人,但应该工作。

 select tempid from temptable left join arrc_vouchers on temptable.tempid = arrc_vouchers.id where arrc_vouchers.id is null 

另一种解决scheme需要查询+一些代码进行一些处理:

 select l.id lValue, c.id cValue, r.id rValue from arrc_vouchers l right join arrc_vouchers c on l.id=IF(c.id > 0, c.id-1, null) left join arrc_vouchers r on r.id=c.id+1 where 1=1 and c.id > 0 and (l.id is null or r.id is null) order by c.id asc; 

请注意,该查询不包含任何我们知道它不被MySQL规划器处理的子查询。

这将返回一个没有更小的值(lValue)或更大的值(rValue)的每个centralValue(cValue)的条目,即:

 lValue |cValue|rValue -------+------+------- {null} | 2 | 3 8 | 9 | {null} {null} | 22 | 23 23 | 24 | {null} {null} | 29 | {null} {null} | 33 | {null} 

没有进一步的细节(我们将在下一段中看到),这个输出意味着:

  • 0和2之间没有值
  • 9和22之间没有值
  • 24和29之间没有值
  • 29和33之间没有数值
  • 33和MAX VALUE之间没有值

所以基本的想法是做一个右和左join同一个表,看看我们是否有邻接值每个值(即:如果中心值是'3',那么我们检查左边3-1 = 2和3 + 1在正确),当一个ROW在RIGHT或LEFT有一个NULL值时,我们知道没有相邻的值。

我的表的完整原始输出是:

 select * from arrc_vouchers order by id asc; 0 2 3 4 5 6 7 8 9 22 23 24 29 33 

一些说明:

  1. 如果将“id”字段定义为UNSIGNED,则需要在联接条件中使用SQL IF语句,因此不允许将其减小到零以下。 如果你保留c.value> 0的话,这并不是必须的,因为它是在下一个注释中说明的,但是我只是把它作为doc来包含它。
  2. 我正在过滤零中心值,因为我们对以前的任何值都不感兴趣,我们可以从下一行导出后置值。

如果你正在使用MariaDB你有一个更快的(800%)选项

 SELECT * FROM seq_1_to_50000 where seq not in (select col from table); 

https://mariadb.com/kb/en/mariadb/sequence/

基于上面Lucek给出的答案,这个存储过程允许你指定你想testing的表和列名来查找非连续的logging – 这样回答了原来的问题,并且演示了如何用@var来表示表, /或存储过程中的列。

 create definer=`root`@`localhost` procedure `spfindnoncontiguous`(in `param_tbl` varchar(64), in `param_col` varchar(64)) language sql not deterministic contains sql sql security definer comment '' begin declare strsql varchar(1000); declare tbl varchar(64); declare col varchar(64); set @tbl=cast(param_tbl as char character set utf8); set @col=cast(param_col as char character set utf8); set @strsql=concat("select ( t1.",@col," + 1 ) as starts_at, ( select min(t3.",@col,") -1 from ",@tbl," t3 where t3.",@col," > t1.",@col," ) as ends_at from ",@tbl," t1 where not exists ( select t2.",@col," from ",@tbl," t2 where t2.",@col," = t1.",@col," + 1 ) having ends_at is not null"); prepare stmt from @strsql; execute stmt; deallocate prepare stmt; end 

这可能不适用于MySQL,但在工作(甲骨文)我们需要类似的东西。

我们写了一个Stored Proc,它把一个数字作为最大值。 存储过程然后创build一个单列的临时表。 该表包含从1到最大的所有数字。 然后它在临时表和我们感兴趣的表之间做了一个NOT IN连接。

如果你使用Max = Select从arrc_vouchers中selectmax(id),它会返回所有的缺失值。

虽然这些似乎都起作用,但是当有50,000条logging时,结果集会在很长的时间内返回。

我用这个,它发现差距或下一个可用(最后使用+ 1)从查询更快的返回。

 SELECT a.id as beforegap, a.id+1 as avail FROM table_name a where (select b.id from table_name b where b.id=a.id+1) is null limit 1; 

您可以使用生成系列来生成从1到您的表的最高ID的数字。 然后运行一个查询,其中id不在这个系列中。