Postgresql将列类型从int更改为UUID

时间:2013-12-03 04:08:18

标签: postgresql

我想将列类型从int更改为uuid。我正在使用以下声明

ALTER TABLE tableA ALTER COLUMN colA SET DATA TYPE UUID;

但我收到错误消息

ERROR:  column "colA" cannot be cast automatically to type uuid
HINT:  Specify a USING expression to perform the conversion.

我很困惑如何使用USING进行演员表。

7 个答案:

答案 0 :(得分:38)

你不能只将int4强制转换为uuid;它是一个无效的uuid,只设置32位,高96位为零。

如果要生成新的UUID以完全替换整数,并且如果没有对这些整数的现有外键引用,则可以使用实际生成新值的伪转换。

如果没有备份数据,请不要运行。它会永久丢弃colA中的旧值。

ALTER TABLE tableA ALTER COLUMN colA SET DATA TYPE UUID USING (uuid_generate_v4());

更好的方法通常是添加一个uuid列,然后修复任何指向它的外键引用,最后删除原始列。

您需要安装UUID模块:

CREATE EXTENSION "uuid-ossp";

报价很重要。

答案 1 :(得分:5)

我必须从文本转换为uuid类型,以及从Django迁移,所以在解决这个问题后,我在http://baltaks.com/2015/08/how-to-change-text-fields-to-a-real-uuid-type-for-django-and-postgresql写了一下,以防万一。相同的技术适用于整数到uuid的转换。

根据评论,我在这里添加了完整的解决方案:

Django很可能会为您创建一个类似于:

的迁移
class Migration(migrations.Migration):

    dependencies = [
        ('app', '0001_auto'),
    ]

    operations = [
        migrations.AlterField(
            model_name='modelname',
            name='uuid',
            field=models.UUIDField(db_index=True, unique=True),
        ),
    ]

首先,将自动创建的迁移操作作为state_operations参数放入RunSQL操作中。这允许您提供自定义迁移,但让Django了解数据库模式发生了什么。

class Migration(migrations.Migration):

    dependencies = [
        ('app', '0001_auto'),
    ]

    operations = [
    migrations.RunSQL(sql_commands, None, [
            migrations.AlterField(
                model_name='modelname',
                name='uuid',
                field=models.UUIDField(db_index=True, unique=True),
            ),
        ]),
    ]

现在,您需要为该sql_commands变量提供一些SQL命令。我选择将sql放入一个单独的文件中,然后使用以下python代码加载:

sql_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '0001.sql')
with open(sql_path, "r") as sqlfile:
    sql_commands = sqlfile.read()

现在是真正棘手的部分,我们实际执行迁移。您想要的基本命令如下:

alter table tablename alter column uuid type uuid using uuid::uuid;

但我们在这里的原因是因为索引。正如我发现的那样,Django喜欢在运行测试时使用迁移在字段上创建随机命名的索引,因此如果只删除然后重新创建一个或两个固定名称索引,测试将失败。所以以下是在转换为uuid字段之前将删除文本字段上的一个约束和所有索引的sql。它也可以一次用于多个表。

DO $$
DECLARE
    table_names text[];
    this_table_name text;
    the_constraint_name text;
    index_names record;

BEGIN

SELECT array['table1',
             'table2'
             ]
    INTO table_names;


FOREACH this_table_name IN array table_names
LOOP
    RAISE notice 'migrating table %', this_table_name;

    SELECT CONSTRAINT_NAME INTO the_constraint_name
    FROM information_schema.constraint_column_usage
    WHERE CONSTRAINT_SCHEMA = current_schema()
        AND COLUMN_NAME IN ('uuid')
        AND TABLE_NAME = this_table_name
    GROUP BY CONSTRAINT_NAME
    HAVING count(*) = 1;
    if the_constraint_name is not NULL then
        RAISE notice 'alter table % drop constraint %',
            this_table_name,
            the_constraint_name;
        execute 'alter table ' || this_table_name
            || ' drop constraint ' || the_constraint_name;
    end if;

    FOR index_names IN
    (SELECT i.relname AS index_name
     FROM pg_class t,
          pg_class i,
          pg_index ix,
          pg_attribute a
     WHERE t.oid = ix.indrelid
         AND i.oid = ix.indexrelid
         AND a.attrelid = t.oid
         AND a.attnum = any(ix.indkey)
         AND t.relkind = 'r'
         AND a.attname = 'uuid'
         AND t.relname = this_table_name
     ORDER BY t.relname,
              i.relname)
    LOOP
        RAISE notice 'drop index %', quote_ident(index_names.index_name);
        EXECUTE 'drop index ' || quote_ident(index_names.index_name);
    END LOOP; -- index_names

    RAISE notice 'alter table % alter column uuid type uuid using uuid::uuid;',
        this_table_name;
    execute 'alter table ' || quote_ident(this_table_name)
        || ' alter column uuid type uuid using uuid::uuid;';
    RAISE notice 'CREATE UNIQUE INDEX %_uuid ON % (uuid);',
        this_table_name, this_table_name;
    execute 'create unique index ' || this_table_name || '_uuid on '
        || this_table_name || '(uuid);';

END LOOP; -- table_names

END;
$$

答案 2 :(得分:3)

如果有人遇到这个老话题。我通过首先将字段更改为CHAR类型然后更改为UUID类型来解决问题。

答案 3 :(得分:2)

我在很长一段时间后碰到这个,但有一种方法可以将整数列转换为具有某种向后兼容性的UUID,即保留一种方法来引用旧值,而不是放弃你的价值观。 它包括将整数值转换为十六进制字符串,然后用必要的零填充它以组成一个人工UUID。

因此,假设您当前的整数列名为ColA,以下语句将执行此操作(请注意using部分):

ALTER TABLE tableA ALTER COLUMN ColA SET DATA TYPE UUID USING LPAD(TO_HEX(ColA), 32, '0')::UUID;

答案 4 :(得分:2)

我可以使用以下过程来转换INT类型的列,并使用SERIAL速记将其配置为递增主键:

--  Ensure the UUID extension is installed.
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

--  Dropping and recreating the default column value is required because
--  the default INT value is not compatible with the new column type.
ALTER TABLE table_to_alter ALTER COLUMN table_id DROP DEFAULT, 
ALTER COLUMN table_id SET DATA TYPE UUID USING (uuid_generate_v4()), 
ALTER COLUMN table_id SET DEFAULT uuid_generate_v4();

答案 5 :(得分:1)

在PostgreSQL 9.3中,你可以这样做:

ALTER TABLE "tableA" ALTER COLUMN "ColA" SET DATA TYPE UUID USING "ColA"::UUID;

并将数据类型转换为UUID,这样可以避免错误消息。

答案 6 :(得分:0)

警告:我注意到一些注释和答案,试图将整数转换为UUID4。

不得投射或强制设置uuid值。它们必须使用与RFC4122相关的函数来生成。

UUID必须随机分布,否则将不起作用。您无法转换或输入自己的UUID,因为它们不会正确分发。这可能导致不良行为者猜测您的排序或在您的UUID中找到其他工件或模式,从而导致他们发现其他对象。

任何将答案转换为char类型然后转换为uuid的答案都可能导致这些类型的问题。

在此处遵循任何有关“ uuid_generate_v4 ”的答案。忽略那些不使用形式函数的强制转换或设置。