如何在MySQL中找到顺序编号中的空白?


119

我们有一个带有表的数据库,该表的值是从另一个系统导入的。有一个自动增量列,没有重复的值,但缺少值。例如,运行以下查询:

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

应该返回100,但返回87。我可以运行任何查询来返回缺失数字的值吗?例如,可能存在ID为1-70和83-100的记录,但是没有ID为71-82的记录。我想返回71、72、73等。

这可能吗?


这可能在MySQL中不起作用,但是在工作中(Oracle)我们需要类似的东西。我们编写了一个存储过程,该过程以数字作为最大值。然后,Stored Proc用一个列创建一个临时表。该表包含从1到Max的所有数字。然后,它在临时表和我们感兴趣的表之间进行了NOT IN连接。如果使用max =从arrc_vouchers中选择max(id)进行调用,则它将返回所有缺少的值。
saunderl

2
编号之间有间隙怎么办?代理键的值通常没有意义;重要的是它的独特性。如果您的应用程序无法处理非连续的ID,则可能是应用程序中的错误,而不是数据中的错误。
Wyzard '12

4
在这种情况下,这是一个问题,因为我们从旧系统继承的数据使用与记录关联的自动递增编号作为键,以在分发给人们的物理卡上进行打印。这不是我们的想法。为了找出缺少的卡,我们需要知道顺序编号中的间隙在哪里。
EmmyS

xaprb.com/blog/2005/12/06/... select l.id + 1 as start from sequence as l left outer join sequence as r on l.id + 1 = r.id where r.id is null;

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

Answers:


170

更新资料

ConfexianMJS 在性能方面提供了更好的 答案

(不尽快)的答案

以下版本适用于任何大小的表格(不仅限于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

6
我什至不再在那家公司工作,但这是我见过的最好的答案,绝对值得记住,以备将来参考。谢谢!
艾美奖

4
唯一的问题是,它不会“报告”可能的初始差距。例如,如果缺少前5个ID(从1到5),这并不表示...我们如何在一开始就显示出明显的差距?
DiegoDD

注意:此查询不适用于临时表。我的问题是,order number我要寻找的缺口不是很明显(表存储订单行,因此它们所属的订单号会在每行中重复)。第一个查询:设置2812行(1分31.09秒)。通过选择不同的订单号制作另一个表格。您的查询没有我的重复:设置了1009行(18.04秒)
Chris K

1
@DiegoDD有什么问题SELECT MIN(id) FROM table
航空

8
工作,但花了大约5个小时在具有700000条记录的桌子上运行
Matt

98

这对我来说很有效,可以找到表中包含超过8万行的空白:

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开始并且没关系,则可以替换

(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)

如果您需要对丢失的ID执行某种shell脚本任务,则也可以使用此变体以便直接生成可以在bash中迭代的表达式。

SELECT GROUP_CONCAT(IF(z.got-1>z.expected, CONCAT('$(',z.expected,' ',z.got-1,')'), z.expected) SEPARATOR " ") AS missing
FROM (  SELECT   @rownum:=@rownum+1 AS expected,   IF(@rownum=height, 0, @rownum:=height) AS got  FROM   (SELECT @rownum:=0) AS a   JOIN block   ORDER BY height  ) AS z WHERE z.got!=0;

产生这样的输出

$(seq 1 99) $(seq 666 667) 50000 $(seq 66419 66456)

然后,您可以将其复制并粘贴到bash终端的for循环中,以对每个ID执行命令

for ID in $(seq 1 99) $(seq 666 667) 50000 $(seq 66419 66456); do
  echo $ID
  # fill the gaps
done

与上面相同,只是可读性和可执行性均相同。通过更改上面的“ CONCAT”命令,可以为其他编程语言生成语法。甚至SQL。


8
很好的解决方案,对我而言,它比首选答案要好-谢谢
Wee Zel

6
它的很多比接受的答案更有效。
symcbean '16

1
高于接受的答案更快。我要添加的唯一一件事是,CONVERT( YourCol, UNSIGNED )如果YourCol还不是整数,它将提供更好的结果。
巴顿·奇滕登

1
@AlexandreCassagne:如果我正确地理解了您的问题,我会简单地做一个类似于嵌入式查询的查询来查找分钟数:SELECT MAX(YourCol) FROM YourTable;
ConfexianMJS

1
@temuri如果需要,请切换到GROUP_CONCAT变体:SELECT IF((z.got-IF(z.over>0, z.over, 0)-1)>z.expected, CONCAT(z.expected,' thru ',(z.got-IF(z.over>0, z.over, 0)-1)), z.expected) AS missing FROM ( SELECT @rownum:=@rownum+1 AS expected, @target-@missing AS under, (@missing:=@missing+IF(@rownum=YourCol, 0, YourCol-@rownum))-@target AS over, IF(@rownum=YourCol, 0, @rownum:=YourCol) AS got FROM (SELECT @rownum:=0, @missing:=0, @target:=10) AS a JOIN YourTable ORDER BY YourCol ) AS z WHERE z.got!=0 AND z.under>0;
ConfexianMJS

11

快速和肮脏的查询应该可以解决问题:

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的ID和存在的next_id,以及之间缺少多少个...

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

1
这对我来说很棒。谢谢。!我可以轻松地针对自己的目的进行修改。
Rahim Khoja

寻找差距中的“下一个ID”时,这似乎是最佳答案。不幸的是,对于行数为10K的表,这非常慢。我一直在〜46K的桌子上等待10分钟以上,而使用@ConfexianMJS可以在不到一秒钟的时间内得到结果!
BringBackCommodore64 '17

5

如果您使用,MariaDB则可以使用序列存储引擎更快(800%)的选择:

SELECT * FROM seq_1_to_50000 WHERE SEQ NOT IN (SELECT COL FROM TABLE);

2
为了扩展这个想法,可以使用序列的最大值,"SELECT MAX(column) FROM table"并从结果中设置一个变量,例如说$ MAX ...,然后可以编写sql语句,"SELECT * FROM seq_1_to_". $MAX ." WHERE seq not in (SELECT column FROM table)" 我的语法是基于php的
me_

或者您可以使用 SELECT @var:= max FROM ....; select * from .. WHERE seq < @max;MySQL变量。
Moshe L

2

创建一个包含100行和一个包含值1-100的单列的临时表。

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

对这个盲人进行编码,但应该可以。

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

好的,1-100只是举个例子的简单方法。在这种情况下,我们正在研究20,000-85,000。那么我是否要创建一个包含65,000行,编号为20000-85000的临时表?我该怎么做呢?我正在使用phpMyAdmin; 如果我将列的默认值设置为25000并使其自动递增,是否可以仅插入65,000行,它将以25000开始自动递增?
EmmyS 2010年

我遇到了类似的情况(我有100项订单,需要在100项中查找缺失的项目)。为此,我创建了另一个表1-100,然后在其上执行此语句,它可以正常工作。这代替了创建临时表的非常复杂的功能。只是为处于类似情况的人提供建议,创建表有时比临时表要快。
newshorts 2014年

2

需要查询+一些代码进行某些处理的替代解决方案是:

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的计划者有效地处理。

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

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之间的值

因此,基本思想是对同一张表进行RIGHT和LEFT连接,看每个值是否有相邻值(即:如果中心值是'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>保持为下一个注释中所述的值,那么这并不是绝对必要的,但是我将它作为doc包含在内。
  2. 我正在过滤零中心值,因为我们对任何先前的值都不感兴趣,因此我们可以从下一行获取发布值。

2

如果序列中两个数字之间的间隔最大为1(例如1,3,5,6),则可以使用的查询为:

select s.id+1 from source1 s where s.id+1 not in(select id from source1) and s.id+1<(select max(id) from source1);
  • table_name- source1
  • column_name- id

1

根据Lucek上面给出的答案,此存储过程使您可以指定要测试以查找非连续记录的表和列名称-从而回答了原始问题,还演示了如何使用@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

1

我以不同的方式进行了尝试,发现的最佳性能是以下简单查询:

select a.id+1 gapIni
    ,(select x.id-1 from arrc_vouchers x where x.id>a.id+1 limit 1) gapEnd
    from arrc_vouchers a
    left join arrc_vouchers b on b.id=a.id+1
    where b.id is null
    order by 1
;

...一个左联接检查下一个ID是否存在,只有在找不到下一个ID的情况下,子查询才能找到存在的下一个ID以查找间隙的末尾。我这样做是因为使用等于(=)的查询比大于(>)运算符的性能更好。

使用sqlfiddle,它不会显示其他查询的不同性能,但是在实际数据库中,该查询的结果比其他查询快3倍。

模式:

CREATE TABLE arrc_vouchers (id int primary key)
;
INSERT INTO `arrc_vouchers` (`id`) VALUES (1),(4),(5),(7),(8),(9),(10),(11),(15),(16),(17),(18),(19),(20),(21),(22),(23),(24),(25),(26),(27),(28),(29)
;

按照下面的所有查询来比较性能:

select a.id+1 gapIni
    ,(select x.id-1 from arrc_vouchers x where x.id>a.id+1 limit 1) gapEnd
    from arrc_vouchers a
    left join arrc_vouchers b on b.id=a.id+1
    where b.id is null
    order by 1
;
select *, (gapEnd-gapIni) qt
    from (
        select id+1 gapIni
        ,(select x.id from arrc_vouchers x where x.id>a.id limit 1) gapEnd
        from arrc_vouchers a
        order by id
    ) a where gapEnd <> gapIni
;
select id+1 gapIni
    ,(select x.id from arrc_vouchers x where x.id>a.id limit 1) gapEnd
    #,coalesce((select id from arrc_vouchers x where x.id=a.id+1),(select x.id from arrc_vouchers x where x.id>a.id limit 1)) gapEnd
    from arrc_vouchers a
    where id+1 <> (select x.id from arrc_vouchers x where x.id>a.id limit 1)
    order by id
;
select id+1 gapIni
    ,coalesce((select id from arrc_vouchers x where x.id=a.id+1),(select x.id from arrc_vouchers x where x.id>a.id limit 1)) gapEnd
    from arrc_vouchers a
    order by id
;
select id+1 gapIni
    ,coalesce((select id from arrc_vouchers x where x.id=a.id+1),concat('*** GAT *** ',(select x.id from arrc_vouchers x where x.id>a.id limit 1))) gapEnd
    from arrc_vouchers a
    order by id
;

也许它对某人有用并且有用。

您可以使用以下sqlfiddle查看和测试我的查询:

http://sqlfiddle.com/#!9/6bdca7/1


0

尽管所有这些似乎都有效,但是当有50,000条记录时,结果集将在很长的时间内返回。

我使用了它,它找到间隔或下一个可用的(最近使用+ 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;

这找到了第一个差距,而这个差距并不是问题所要的。
drewish

0

可能不相关,但是我一直在寻找类似的内容,以便按数字顺序列出差距,并找到了这篇文章,根据您的实际需求提供了多种不同的解决方案。我一直在寻找序列中的第一个可用间隔(即下一个可用数字),这似乎很好。

SELECT MIN(l.number_sequence + 1)作为下一个对l.number_sequence + 1 = r.number_sequence的患者在l.number_sequence上为r。从2005年开始,这里讨论了其他几种方案和解决方案!

如何使用SQL查找序列中的缺失值

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.