Postgresql change column type from int to UUID

后端 未结 7 1583
爱一瞬间的悲伤
爱一瞬间的悲伤 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: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;
    $$
    

提交回复
热议问题