9

我有一个数据库,我想将其转换为使用 UUID 作为 postgresql 中的主键。

我有大约 30 个具有深度多级关联的表。是否有一种“简单”的方法可以将所有当前 ID 转换为 UUID?

从这里: https://coderwall.com/p/n_0awq,我可以看到我可以在迁移中更改表。我在想这样的事情:

for client in Client.all
  # Retrieve children
  underwritings = client.underwritings
  # Change primary key
  execute 'ALTER TABLE clients ALTER COLUMN id TYPE uuid;'
  execute 'ALTER TABLE clients ALTER COLUMN id SET DEFAULT uuid_generate_v1();'
  # Get new id - is this already generated?
  client_id = client.id
  for underwriting in underwritings
    locations = underwriting.locations
    other_record = underwriting.other_records...

    execute 'ALTER TABLE underwritings ALTER COLUMN id TYPE uuid;'
    execute 'ALTER TABLE underwritings ALTER COLUMN id SET DEFAULT uuid_generate_v1();'
    underwriting.client_id = client_id
    underwriting.saved
    underwriting_id = underwriting.id

    for location in locations
      buildings = location.buildings
      execute 'ALTER TABLE locations ALTER COLUMN id TYPE uuid;'
      execute 'ALTER TABLE locations ALTER COLUMN id SET DEFAULT uuid_generate_v1();'
      location.undewriting_id = underwriting_id
      location.save
      location_id = location.id

      for building in buildings
      ...
      end
    end
    for other_record in other_records
      ...
    end
    ... 
    ...
  end
end

问题:

  • 这行得通吗?
  • 有没有更简单的方法来做到这一点?
  • 只要在更改主键之前检索子记录,是否会正确检索子记录?
  • 一旦调用了alter table,是否已经生成了新的主键?

非常感谢您提供的任何帮助或提示。

4

3 回答 3

9

我发现这些非常乏味。可以使用对 PostgreSQL 的直接查询来使用现有数据转换表。

对于主键:

    ALTER TABLE students
        ALTER COLUMN id DROP DEFAULT,
        ALTER COLUMN id SET DATA TYPE UUID USING (uuid(lpad(replace(text(id),'-',''), 32, '0'))),
        ALTER COLUMN id SET DEFAULT uuid_generate_v4()

对于其他参考:

    ALTER TABLE students
        ALTER COLUMN city_id SET DATA TYPE UUID USING (uuid(lpad(replace(text(city_id),'-',''), 32, '0')))

左上角用零填充整数值并转换为 UUID。这种方法不需要 id 映射,如果需要,可以检索旧 id。

由于没有数据复制,这种方法工作得非常快。

要处理这些和更复杂的多态关联情况,请使用https://github.com/kreatio-sw/webdack-uuid_migration。这个 gem 为 ActiveRecord::Migration 添加了额外的助手来简化这些迁移。

于 2014-01-25T15:00:35.823 回答
4

我认为尝试通过 Rails 做这样的事情只会使事情复杂化。我会完全忽略 Rails 方面的事情,只用 SQL 来做。

您的第一步是获取数据库的完整备份。然后将该备份还原到另一个数据库以:

  1. 确保您的备份有效。
  2. 给你一个逼真的围栏,在那里你可以犯错而不会产生后果。

首先,您需要通过添加真正的外键来匹配所有 Rails 关联来清理数据。您的某些 FK 很有可能会失败,如果确实如此,您将不得不清理损坏的引用。

现在您有了干净的数据,重命名所有表以为新的 UUID 版本腾出空间。对于一个表t,我们将重命名的表称为t_tmp. 对于 each t_tmp,创建另一个表来保存从旧integer ids 到新 UUID的映射id,如下所示:

create table t_id_map (
    old_id integer not null,
    new_id uuid not null default uuid_generate_v1()
)

然后填充它:

insert into t_id_map (old_id)
select id from t_tmp

t_id_map.old_id当你在这里时,你可能想要索引。

这为我们提供了带有integerid 的旧表和一个将旧表t_tmp映射id到新表的查找表。

现在使用 UUID 创建新表,替换所有旧的integerserial包含 s 的列id;此时我也会添加真正的外键;你应该对你的数据保持偏执:损坏的代码是暂时的,损坏的数据通常是永远的。

此时填充新表非常容易:只需使用insert into ... select ... from构造和 JOIN 到适当的t_id_map表来将旧id的 s 映射到新的表。映射和复制数据后,您需要进行一些健全性检查以确保一切仍然有意义。然后你可以放下你的t_tmp桌子t_id_map,继续你的生活。

在您的数据库副本上练习该过程,编写脚本,然后离开。

您当然希望在执行此工作时关闭任何访问您的数据库的应用程序。

于 2013-06-21T21:08:02.150 回答
1

不想添加外键,并想使用 rails 迁移。无论如何,如果其他人想要这样做,这就是我所做的(例如 2 个表,我总共做了 32 个):

  def change
    execute 'CREATE EXTENSION "uuid-ossp";'
    execute <<-SQL
      ALTER TABLE buildings ADD COLUMN guid uuid DEFAULT uuid_generate_v1() NOT NULL;
      ALTER TABLE buildings ALTER COLUMN guid SET DEFAULT uuid_generate_v1();
      ALTER TABLE buildings ADD COLUMN location_guid uuid;

      ALTER TABLE clients ADD COLUMN guid uuid DEFAULT uuid_generate_v1() NOT NULL;
      ALTER TABLE clients ALTER COLUMN guid SET DEFAULT uuid_generate_v1();
      ALTER TABLE clients ADD COLUMN agency_guid uuid;
      ALTER TABLE clients ADD COLUMN account_executive_guid uuid;
      ALTER TABLE clients ADD COLUMN account_representative_guid uuid;
    SQL

    for record in Building.all
      location = record.location
      record.location_guid = location.guid

      record.save
    end

    for record in Client.all
      agency = record.agency
      record.agency_guid = agency.guid

      account_executive = record.account_executive
      record.account_executive_guid = account_executive.guid unless account_executive.blank?

      account_representative = record.account_representative
      record.account_representative_guid = account_representative.guid unless account_representative.blank?

      record.save
    end

    execute <<-SQL
      ALTER TABLE buildings DROP CONSTRAINT buildings_pkey;
      ALTER TABLE buildings DROP COLUMN id;
      ALTER TABLE buildings RENAME COLUMN guid TO id;
      ALTER TABLE buildings ADD PRIMARY KEY (id);
      ALTER TABLE buildings DROP COLUMN location_id;
      ALTER TABLE buildings RENAME COLUMN location_guid TO location_id;

      ALTER TABLE clients DROP CONSTRAINT clients_pkey;
      ALTER TABLE clients DROP COLUMN id;
      ALTER TABLE clients RENAME COLUMN guid TO id;
      ALTER TABLE clients ADD PRIMARY KEY (id);
      ALTER TABLE clients DROP COLUMN agency_id;
      ALTER TABLE clients RENAME COLUMN agency_guid TO agency_id;
      ALTER TABLE clients DROP COLUMN account_executive_id;
      ALTER TABLE clients RENAME COLUMN account_executive_guid TO account_executive_id;
      ALTER TABLE clients DROP COLUMN account_representative_id;
      ALTER TABLE clients RENAME COLUMN account_representative_guid TO account_representative_id;
    SQL
  end
于 2013-07-21T21:32:09.717 回答