【问题标题】:MySQL fixing autoincrement gaps in two tablesMySQL修复两个表中的自动增量差距
【发布时间】:2012-06-20 00:43:48
【问题描述】:

我有两张这样的桌子;

id_image   foo    bar
1          3      5
2          8      1
3          17     88
7          14     23
8          12     9


id_image   bar    foo
1          2      3
1          5      6
2          18     11
2          10     12
3          8      21
3          17     81
7          29     50
7          1      14
8          10     26
8          27     34

第一个表中自动递增的id_image 存在间隙。在第二个表中,id_image 指的是第一个表中的id_image,每个 ID 都有两个。

注意:此表为理论值。我不知道在哪里间隙究竟在哪里,或者是否甚至有多个间隙。我只知道第一个值是1,最后一个值高于总行数。

现在,我想弥补这个差距。

在你说差距无关紧要之前,如果他们这样做,那就是糟糕的数据库设计,让我告诉你; 我同意你的看法。

但是,我正在处理的是一个(无可救药的后端)第三方开源系统,我需要将大量没有可交叉引用 ID 的现有数据导入到多个表中。我可以确保相同数据在整个系统的每个表中获得匹配 ID 的唯一方法是按顺序输入,这意味着我不能有间隙。

所以我现在需要做的是;

  1. 修复第一个表中id_image列的间隙,使最后一个值与行数匹配。
  2. 编辑第二个表中的id_image 列,使其值对应于间隙修复之前对应的同一行。

我将如何开始这样做?我知道这可能超出了 MySQL 查询语言的能力,所以 PHP 的答案也是可以接受的。谢谢! :)

【问题讨论】:

  • 是的。然而,差距是由于必须重新输入部分数据造成的,而不是由 INSERT IGNORE 错误造成的。 :)
  • 你能锁定表格,然后用SELECT image_id FORM table ORDER BY image_id ASC 遍历它并找出差距吗?
  • 您的意思是手动查找它们?有超过 20,000 条记录,我有一个截止日期:P
  • @EmphramStavanger 不。让我发布一个解决方案来演示它。
  • 在第二个表中是否总是有两个表中每个 id 的副本?

标签: php mysql database auto-increment


【解决方案1】:
ALTER TABLE table2
ADD FOREIGN KEY FK_IMAGE (id_image)
REFERENCES table1 (id_image)
ON DELETE CASCADE
ON UPDATE CASCADE;

SET @currentRow = 0;

UPDATE table1 INNER JOIN (
    SELECT @currentRow := @currentRow + 1 AS id_image_new, id_image AS id_image_old
    FROM table1
    ORDER BY id_image ASC) t on t.id_image_old = table1.id_image
SET table1.id_image = t.id_image_new;

ALTER TABLE table1 AUTO_INCREMENT = 1;

FK 将相应地自动更新您的第二张表的 ID。

我完全不确定,但在某些旧版本的 mysql 中,更新您在更新的子查询中引用的表可能会崩溃。如果是这样,只需创建第二个表并填充(插入),然后删除旧表并重命名新表。

【讨论】:

  • +1 必须一劳永逸。但是请注意,需要在第一个表上重新设置身份,否则下一个插入会留下一个空白。
  • #1452 - 无法添加或更新子行:外键约束失败 (table2, CONSTRAINT table2_ibfk_1 FOREIGN KEY (id_image) REFERENCES table1 (table1) ON DELETE CASCADE ON UPDATE CASCADE) -- 这里有什么问题?
  • 嗬嗬,这意味着另外,table2的某些行没有父母^^
  • 创建密钥时会发生这种情况吗?
  • 我在 phpMyAdmin 的 SQL 控制台中运行整个代码,这就是我得到的错误。表格没有发生明显变化。
【解决方案2】:

这很痛苦。

像第一个一样创建一个表,在 Id_Image 上没有标识和一个名为 rownumber 的额外 int 列

使用伪 row_number 技巧来填充它,有些像

Insert into NewTable
Select id_image,foo,bar,@RowNumber := @RowNumber + 1 order by id_image.

如果您有第二个表的外键删除它,那么这是一个简单的连接更新。删除旧表 1 ,重命名新表,添加身份和重新种子,如果有外键,则将其放回。

你确实意识到你将不得不继续做这些废话?

如果您有级联更新,可能有一种有趣的方法可以一次性完成所有这些操作,但请注意执行计划。 RowNumber 技巧仅在事情以 Id_Image 顺序完成时才有效。如果 Mysql 决定有一种更有效的查询方式......

【讨论】:

    【解决方案3】:

    这里的基本思想是首先找到所有的差距,以确定你需要减少每个 id 多少。然后,您必须遍历两个表并应用减量。 (您需要添加:host、db、user、pass 和实际的表名)

    try {
        $pdo = new PDO('mysql:host=HOST;dbname=DB', 'user', 'pass');
    
        $pdo->beginTransaction();
    
        // Iterate through all id's in the first table
        $stmt = $pdo->exec('SELECT image_id FROM TableOne ORDER BY image_id ASC');
        $stmt->bindColumn('image_id', $id);
    
        if(!$stmt->fetch(PDO::FETCH_BOUND)) {
            throw Exception('No rows in table');
        }
    
        $lastId = $id;
        $gaps = array();
    
        // Find all the gaps
        while($stmt->fetch(PDO::FETCH_BOUND)) {
            if($id != ($lastId + 1)) {
                $gaps[] = $id;
            }
    
            $lastId = $id;
        }
    
    
        if(!isset($gaps[0])) {
            throw new Exception('No gaps found');
        }
    
        // For each gap, update the range from the last gap to that gap by subtracting
        // the number of gaps there has been from the id
        $lastGap = $gaps[0];
    
        for($i = 1; $i < count($gaps); $i++) {
            $stmt = $pdo->prepare('UPDATE TableOne SET image_id = image_id - :i WHERE image_id BETWEEN :lastGap AND :gap');
            $stmt->execute(array(
                ':i' => $i,
                ':lastGap' => $lastGap,
                ':gap' => $gaps[$i]
            ));
    
            $stmt = $pdo->prepare('UPDATE TableTwo SET image_id = image_id - :i WHERE image_id BETWEEN :lastGap AND :gap');
            $stmt->execute(array(
                ':i' => $i,
                ':lastGap' => $lastGap,
                ':gap' => $gaps[$i]
            ));
    
            $lastGap = $gaps[$i];
        }
    
        // Finally, fix the gap between the last found gap and the end of the table
        $stmt = $pdo->prepare('UPDATE TableOne SET image_id = image_id - :i WHERE image_id BETWEEN :lastGap AND :gap');
        $stmt->execute(array(
            ':i' => $i,
            ':lastGap' => $lastGap,
            ':gap' => $gaps[$i]
        ));
    
        $stmt = $pdo->prepare('UPDATE TableTwo SET image_id = image_id - :i WHERE image_id BETWEEN :lastGap AND :lastId');
        $stmt->execute(array(
            ':i' => $i,
            ':lastGap' => $lastGap,
            ':lastId' => $lastId
        ));
    
        // Verify everything is correct
        $stmt = $pdo->exec('SELECT image_id FROM TableOne ORDER BY image_id ASC');
        $stmt->bindColumn('image_id', $id);
    
        if(!$stmt->fetch(PDO::FETCH_BOUND)) {
            throw new Exception('No rows'); // Should never be thrown
        }
    
        $lastId = $id;
    
        while($stmt->fetch(PDO::FETCH_BOUND)) {
            if($id != ($lastId + 1)) {
                throw new Exception('There was an error between ids ' . $lastId . ' and '. $id);
            }
    
            $lastId = $id;
        }
    
        $stmt = $pdo->exec('SELECT image_id FROM TableTwo ORDER BY image_id ASC');
        $stmt->bindColumn('image_id', $id);
    
        if(!$stmt->fetch(PDO::FETCH_BOUND)) {
            throw new Exception('No rows in table two'); // Shouldn't hit this
        }
    
        $lastId = $id;
        $ids = array($id);
    
        while($stmt->fetch(PDO::FETCH_BOUND)) {
            $ids[] = $id;
    
            if(count($ids) == 2) {
                if($ids[0] !== $ids[1]) {
                    throw new Exception('Table two error on ids ');
                }
    
                if($ids[0] !== $lastId) {
                    throw new Exception('Table two error on id gapfix');
                }
    
                $lastId = $ids[0];
                $ids = array();
            }
        }
    
        $pdo->commit();
    } catch(Exception $e) {
        $pdo->rollBack();
    
        var_dump($e);
    }
    

    重要提示: 您可能希望将其放入文件中并通过 CLI 运行:php -f gapfix.php 并在 $pdo-&gt;commit() 之前包含一个查询,该查询返回所有 id 的列表,因此您可以验证操作是否按预期工作。如果没有,您可以将其回滚,就好像什么都没发生一样。 代码现在会自行检查第一个表的顺序是否正确。但是它还没有检查第二个表。 所有检查都已实施!

    【讨论】:

    • @EmphramStavanger 已实施!
    • 我假设$pdo-&gt;execute() 命令应该是$pdo-&gt;exec(),并且if 在它缺少对象名称上的$ 之后不久。但是,我仍然收到 500 内部服务器错误,并且我的 httpd 错误日志由于某种原因是空的。你知道怎么回事吗?
    • @EmphramStavanger 是的。未编译代码上的粗心错误(如果没有您的数据库,我无法对此进行测试)。您可能会收到 500,因为您的服务器配置为在 PHP 错误时发送 500。我已经更新了我的代码,希望能修复这些更改。试试看大小。 (您可能希望在运行我的代码之前备份您的数据库以防万一)。
    • 在我发现的最后一个while 上缺少},但即便如此它也无法正常工作。如果您愿意,我可以通过电子邮件将表格发送给您。
    • @EmphramStavanger 定义不起作用。一般来说,在 SO 上发送电子邮件是不被接受的。数据有多敏感?您介意将 SQL 转储上传到 pastebin 吗?
    猜你喜欢
    • 2011-04-10
    • 2012-11-01
    • 2016-06-05
    • 2012-06-23
    • 2011-10-28
    • 2011-12-05
    • 2015-10-04
    • 1970-01-01
    相关资源
    最近更新 更多