【问题标题】:MySQL: Replace all instances of specific foreign key with new valueMySQL:用新值替换特定外键的所有实例
【发布时间】:2015-04-05 08:10:45
【问题描述】:

我有一个 MySQL 数据库,其中包含 1000 条人事记录,而且经常有重复。

对于每个至少有一个重复项的情况,我希望能够删除除一个之外的所有重复项,然后用我没有的那个更新对那些已删除外键的任何引用。

例如,我们在下面看到两个Star Lord 实例:

+-----------------------+
|        `users`        |
+------+----------------+
| id   | name           |
+------+----------------+
| 1    | Star Lord      |
+------+----------------+
| 2    | Star Lord      |
+------+----------------+
| 3    | Iron Man       |
+------+-----+----------+

+-----------------------+
|       `messages`      |
+------+-----+----------+
| from | to  | text     |
+------+-----+----------+
| 1    | 5   | hi       |
+------+-----+----------+
| 2    | 5   | how r u  |
+------+-----+----------+
| 5    | 2   | Good, u? |
+------+-----+----------+

这两个表应该变成:

+-----------------------+
|        `users`        |
+------+----------------+
| id   | name           |
+------+----------------+
| 1    | Star Lord      |
+------+----------------+
| 3    | Iron Man       |
+------+-----+----------+

+-----------------------+
|       `messages`      |
+------+-----+----------+
| from | to  | text     |
+------+-----+----------+
| 1    | 5   | hi       |
+------+-----+----------+
| 1    | 5   | how r u  |
+------+-----+----------+
| 5    | 1   | Good, u? |
+------+-----+----------+

这可以吗?我很乐意根据需要使用 PHP。

我找到了以下内容,但它仅用于查找外键用法,而不是替换特定键值的实例:MySQL: How to I find all tables that have foreign keys that reference particular table.column AND have values for those foreign keys?

奖励积分

可能有额外的数据需要在users 表中合并。例如,ID #1 的Star Lord 可能填充了phone 字段,但ID #2 的Star Lord 具有email 字段。

最坏的情况:他们有一个字段,有冲突的数据。

【问题讨论】:

  • 除非您进行人工干预,否则奖励积分几乎无法解决。你可以做空值,但除非你能为每个冲突想出一个规则,否则这是不可能的。

标签: php mysql foreign-keys


【解决方案1】:

我建议:

  1. 创建一个包含正确数据的表格。一个好的起点可能是:

    CREATE TABLE users_new LIKE users;
    ALTER  TABLE users_new ADD UNIQUE (name);
    
    INSERT INTO users_new
      (id, name, phone, email)
    SELECT   MIN(id), name, GROUP_CONCAT(phone), GROUP_CONCAT(email)
    FROM     users
    GROUP BY name;
    

    请注意,由于您在“奖励积分”下的“最坏情况”观察,您可能希望在归档基础 users 数据之前手动验证此表的内容(我建议不要永久删除,以防万一)。

  2. 更新现有的对外关系:

    UPDATE messages
      JOIN (users uf JOIN users_new unf USING (name)) ON uf.id = messages.from
      JOIN (users ut JOIN users_new unt USING (name)) ON ut.id = messages.to
    SET    messages.from = unf.id,
           messages.to   = unt.id
    

    如果您有很多表要更新,您可以缓存usersusers_new 之间的连接结果——或者:

    • 在旧 users 表中的 new_id 列中:

      ALTER TABLE users ADD new_id BIGINT UNSIGNED;
      
      UPDATE users JOIN users_new USING (name)
      SET    users.new_id = users_new.id;
      
      UPDATE messages
        JOIN users uf ON uf.id = messages.from
        JOIN users ut ON ut.id = messages.to
      SET    messages.from = uf.new_id,
             messages.to   = ut.new_id;
      
    • 或者在一个新的(临时)表中:

      CREATE TEMPORARY TABLE newid_cache (
        PRIMARY KEY(old_id),
        KEY(old_id, new_id)
      ) ENGINE=MEMORY
      SELECT users.id AS old_id, users_new.id AS new_id
      FROM   users JOIN users_new USING (name);
      
      UPDATE messages
        JOIN newid_cache nf ON nf.old_id = messages.from
        JOIN newid_cache nt ON nt.old_id = messages.to
      SET    messages.from = nf.new_id,
             messages.to   = nt.new_id;
      
  3. 要么将users 替换为users_new,要么修改您的应用程序以使用新表代替旧表。

    ALTER TABLE users     RENAME TO users_old;
    ALTER TABLE users_new RENAME TO users;
    
  4. 根据需要更新任何外键约束。

【讨论】:

  • @TonyHopkinson:但原始表永远不会被修改(除了可选的添加new_id 列;以及最后的重命名步骤)——基础数据永远不会被触及。
  • 然后他们删除了 users_old,一天后有人打电话说“我所有的消息”都不见了。然后你进入完全灾难模式。像我这样粗心大意、无能的人,你已经足够优秀了,需要一个后盾。 :(
  • @TonyHopkinson:我的回答已经说“...在归档基础users 数据之前验证此表的内容(我建议不要永久删除,以防万一)。 i>"
  • 嗯态度不同,我建议备份,以防万一,以防万一。没什么大不了的,这只是我的一种反应,被要求撤消原来是无效的数据修复。事实上,我可能也会在之后进行备份。
【解决方案2】:

我喜欢对此有条不紊,虽然您可以在一个复杂的查询中编写所有内容,但这是一种优化,除非很明显,否则是不必要的。

首先备份你的数据库:) Create a table 保存您要保留的用户的 ID。

用说填充它

Insert into Keepers Select keep_id From (Select Min(id) as keep_id,`name` From `users`)

在那之后只是一些关于连接的更新。

例如

UPDATE 
   `messages` m JOIN
   keepers k 
      ON k.keeper_id = m.from 
SET m.from = k.keeper_id

UPDATE 
   `messages` m JOIN
   keepers k 
      ON k.keeper_id = m.to 
SET m.to = k.keeper_id

然后摆脱你不想要的用户

Delete `users`
from `users` u
outer join keepers on k.keeper_id = u.id
where i.id is null

When 一切都很好,例如,您的消息数量与开始时相同,没有人在自言自语等。

Delete the keepers table.

语法未检查,但应该很接近。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-11-20
    • 2018-01-05
    • 2022-06-23
    • 2018-01-10
    • 1970-01-01
    • 2017-02-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多