有效地找到表中不存在的第一个数字?

Posted

技术标签:

【中文标题】有效地找到表中不存在的第一个数字?【英文标题】:efficiently find first number that do not exist in a table? 【发布时间】:2016-01-03 22:25:04 【问题描述】:

我有一个大约 528829 行的表,看起来像

CREATE TABLE `ips` (
  `id` INTEGER PRIMARY KEY AUTOINCREMENT,
  `ip` INTEGER NOT NULL DEFAULT NULL,
  `scantime` INTEGER NULL DEFAULT NULL,
  `pingable` INTEGER NULL DEFAULT NULL
);

现在我需要找到ip 中不存在的第一个数字,从 0 开始到 4294967295(又名 0xFFFFFFFF),

目前我只是使用

function isScanned($ip)
    static $isScannedStm=false;
    static $boundip=0;
    if($isScannedStm===false)
    global $db;
    $isScannedStm=$db->prepare('SELECT 1 FROM `ips` WHERE `ip` = :ip LIMIT 1');
    $isScannedStm->bindParam(':ip',$boundip,PDO::PARAM_INT);
    return isScanned($ip);
    
    $boundip=$ip;
    $isScannedStm->execute();
    //var_dump($isScannedStm->fetch(PDO::FETCH_NUM));
    return !!($isScannedStm->fetch(PDO::FETCH_NUM));

//~~~
    while(isScanned($i))
        ++$i;
    

..它可以工作,但有 528829 行,在我的 Intel Atom C2750 @ 2.4GHz 上需要超过 1 小时 30 分钟。我怎样才能更快地找到这个值?最好快得多?

【问题讨论】:

如果这是您的实际代码,您的创建表 AUTOINCREMENT 应该读作 AUTO_INCREMENT dev.mysql.com/doc/refman/5.7/en/example-auto-increment.html @Fred-ii-nope,我使用 sqlite,在 SQLite 中,它的 AUTOINCREMENT ,他们无法就名称达成一致^^ 啊,我不知道。谢谢(你的)信息。 (注)。 Find mininum not used value in mysql table的可能重复 @JimL 如果您要发布答案,请不要发布重复内容。如果问题因此而关闭,那么没有其他人可以添加额外/替代答案以可能改进问题。这是一种垄断形式。如果我没记错的话,这不是你第一次这样做,也是在你发布答案一分钟后。 【参考方案1】:

我只在 MySQL 中测试过,希望它也适用于 SQLite

SELECT ips.ip+1 AS Missing 
FROM ips
LEFT JOIN ips AS next ON ips.ip+1 = next.ip
WHERE next.ip IS NULL 
ORDER BY ips.ip LIMIT 1;

Caspar 和 splattru 的解决方案:https://***.com/a/6464763/1078488

【讨论】:

@hanshenrik 太棒了!与旧的做事方式相比,性能如何? 1 小时 30 分钟变成了 @hanshenrik 进步很大,如果你遇到卡斯帕,我想你应该喝一杯啤酒 ^^ 很好,遇到(并帮助!)同胞总是很有趣 有趣的统计数据,它始终以 4.0X 秒的速度运行。在 ip 上添加一个 UNIQUE INDEX 会将其降低到一致的 1.3X 秒 ^^ @hanshenrik 结果非常惊人。 Lmk 如果你做一些与 web/php/js 相关的事情,我的聊天列表上总是需要更多的“本地人”^^【参考方案2】:

您可以考虑执行一种“二分搜索”。从连续数字的前半部分开始[1, 2, 3, ... (n/2)]

如果结果的数量不等于当前列表中的连续值的数量,那么您可以拆分初始列表并通过相同的逻辑重新运行,直到您到达第一个不连续的 id。

否则,如果计数匹配,您将转到另一半连续 id。

您的查询将需要包含WHERE...IN 子句。

这不会完全适合你,但也许这会有所帮助:

// Populate current set of consecutive integers
$list = array_fill(0, $count/2);
$listQuery = implode(',', $list);

global $db;

$isScannedStm = $db->prepare('
    SELECT 1 FROM `ips` 
    WHERE `ip` IN ('.$listQuery.') 
    GROUP BY `ip` 
    ORDER BY `ip` ASC
');
$isScannedStm->execute()

// Check num results 
if (count($list) !== $isScannedStm->fetch(PDO::FETCH_NUM)) 
    // Split the initial list in half 
    // OR loop through results and find when the ids are not consecutive   


可能有更简单的方法可以做到这一点,也许可以考虑查看this question

【讨论】:

以上是关于有效地找到表中不存在的第一个数字?的主要内容,如果未能解决你的问题,请参考以下文章

如何有效地检索数字的第一个十进制数字

如何用sql语句查询:在一个表中存在而另一个表中不存在的第一条记录?

有效地找到匹配位掩码的第一个元素

在 IN 表达式中查找不存在记录的 id

mySQL 从一个表中选择,该表在另一个表中不存在,并且不是第三个表中的子表

SQL 查询 (Pro*C) 如何能够找到 db 表中不存在的一组值