Postgresql change column type from int to UUID

后端 未结 7 1584
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-12-23 13:26

I\'d like to change the column type from an int to a uuid. I am using the following statement

ALTER TABLE tableA ALTER COLUMN colA          


        
相关标签:
7条回答
  • 2020-12-23 14:06

    WARNING: I've noticed some comments and answers that try to cast integers to a UUID4.

    You must not cast or force-set uuid values. They must be generated using functions relating to RFC4122.

    UUIDs must be randomly distributed or they will not work. You cannot cast or enter your own UUIDs as they will not be properly distributed. This can lead to bad actors guessing your sequencing or finding other artifacts or patterns in your UUIDs that will lead them to discover others.

    Any answer that converts to char types and then to uuid may lead to these kinds problems.

    Follow any answer here that refers to 'uuid_generate_v4'. Ignore ones that are casting or setting without using the formal functions.

    0 讨论(0)
  • 2020-12-23 14:10

    I had to convert from text to uuid type, and from a Django migration, so after solving this I wrote it up at http://baltaks.com/2015/08/how-to-change-text-fields-to-a-real-uuid-type-for-django-and-postgresql in case that helps anyone. The same techniques would work for an integer to uuid conversion.

    Based on a comment, I've added the full solution here:

    Django will most likely create a migration for you that looks something like:

    class Migration(migrations.Migration):
    
        dependencies = [
            ('app', '0001_auto'),
        ]
    
        operations = [
            migrations.AlterField(
                model_name='modelname',
                name='uuid',
                field=models.UUIDField(db_index=True, unique=True),
            ),
        ]
    

    First, put the auto created migration operations into a RunSQL operation as the state_operations parameter. This allows you to provide a custom migration, but keep Django informed about what's happened to the database schema.

    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),
                ),
            ]),
        ]
    

    Now you'll need to provide some SQL commands for that sql_commands variable. I opted to put the sql into a separate file and then load in with the following python code:

    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()
    

    Now for the real tricky part, where we actually perform the migration. The basic command you want looks like:

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

    But the reason we are here is because of indexes. And as I discovered, Django likes to use your migrations to created randomly named indexes on your fields while running tests, so your tests will fail if you just delete and then recreate a fixed name index or two. So the following is sql that will delete one constraint and all indexes on the text field before converting to a uuid field. It also works for multiple tables in one go.

    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;
    $$
    
    0 讨论(0)
  • 2020-12-23 14:15

    In PostgreSQL 9.3 you can do this:

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

    And cast the type of data to UUID and this will avoid the error message.

    0 讨论(0)
  • 2020-12-23 14:22

    I was able to convert a column with an INT type, configured as an incrementing primary key using the SERIAL shorthand, using the following process:

    --  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();
    
    
    0 讨论(0)
  • 2020-12-23 14:23

    Just if someone comes across this old topic. I solved the problem by first altering the field into a CHAR type and then into UUID type.

    0 讨论(0)
  • 2020-12-23 14:29

    You can't just cast an int4 to uuid; it'd be an invalid uuid, with only 32 bits set, the high 96 bits being zero.

    If you want to generate new UUIDs to replace the integers entirely, and if there are no existing foreign key references to those integers, you can use a fake cast that actually generates new values.

    Do not run this without a backup of your data. It permanently throws away the old values in colA.

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

    A better approach is usually to add a uuid column, then fix up any foreign key references to point to it, and finally drop the original column.

    You need the UUID module installed:

    CREATE EXTENSION "uuid-ossp";
    

    The quotes are important.

    0 讨论(0)
提交回复
热议问题