【问题标题】: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 ,他们无法就名称达成一致^^
  • 啊,我不知道。谢谢(你的)信息。 (注)。
  • @JimL 如果您要发布答案,请不要发布重复内容。如果问题因此而关闭,那么没有其他人可以添加额外/替代答案以可能改进问题。这是一种垄断形式。如果我没记错的话,这不是你第一次这样做,也是在你发布答案一分钟后。

标签: php sql performance sqlite


【解决方案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://stackoverflow.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

【讨论】:

    猜你喜欢
    • 2011-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-07
    • 1970-01-01
    • 2014-12-12
    • 1970-01-01
    相关资源
    最近更新 更多