Postgresql change le type de colonne de int à UUID

Je voudrais changer le type de colonne d'un int à un uuid. J'utilise la déclaration suivante

ALTER TABLE tableA ALTER COLUMN colA SET DATA TYPE UUID;

, Mais je reçois le message d'erreur

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

Je suis confus comment utiliser USING pour faire le casting.

25
demandé sur guaka 2013-12-03 08:08:18

5 réponses

Vous ne pouvez pas simplement lancer un int4 en uuid; ce serait un uuid invalide, avec seulement 32 bits définis, les 96 bits élevés étant nuls.

Si vous voulez générer de nouveaux UUID pour remplacer entièrement les entiers, et s'il n'y a pas de références de clés étrangères existantes à ces entiers, vous pouvez utiliser une fausse distribution qui génère réellement de nouvelles valeurs.

Ne pas faire fonctionner ce, sans une sauvegarde de vos données. Il jette définitivement les anciennes valeurs dans colA.

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

Une meilleure approche est généralement pour ajouter une colonne uuid, puis corriger toutes les références de clé étrangère pour pointer vers elle, et enfin supprimer la colonne d'origine.

Vous avez besoin du module UUID installé:

CREATE EXTENSION "uuid-ossp";

Les citations sont importantes.

34
répondu Craig Ringer 2013-12-03 04:53:19

J'ai dû convertir du texte en type uuid, et à partir d'une migration Django, donc après avoir résolu cela, je l'ai écrit à http://baltaks.com/2015/08/how-to-change-text-fields-to-a-real-uuid-type-for-django-and-postgresql au cas où cela aiderait quelqu'un. Les mêmes techniques fonctionneraient pour une conversion d'entier en uuid.

Basé sur un commentaire, j'ai ajouté la solution complète ici:

Django créera probablement pour vous une migration qui ressemble à quelque chose comme:

class Migration(migrations.Migration):

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

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

Tout d'abord, placez les opérations de migration créées automatiquement dans une opération RunSQL en tant que paramètre state_operations. Cela vous permet de fournir une migration personnalisée, mais gardez Django informé de ce qui est arrivé au schéma de base de données.

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

Maintenant, vous devrez fournir des commandes SQL pour cette variable sql_commands. J'ai choisi de mettre le sql dans un fichier séparé, puis de le charger avec le code python suivant:

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

Maintenant, pour la vraie partie délicate, où nous effectuons réellement migration. La commande de base que vous voulez ressemble à:

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

Mais la raison pour laquelle nous sommes ici est à cause des index. Et comme je l'ai découvert, Django aime utiliser vos migrations pour créer des index nommés aléatoirement sur vos champs lors de l'exécution de tests, de sorte que vos tests échoueront si vous supprimez et recréez un index de nom fixe ou deux. Donc, ce qui suit est sql qui va supprimer une contrainte et tous les index sur le champ de texte avant de convertir en un champ uuid. Il fonctionne également pour plusieurs tables en une seule fois.

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;
$$
4
répondu Michael Baltaks 2015-08-28 15:01:53

Dans PostgreSQL 9.3, vous pouvez faire ceci:

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

Et convertit le type de données en UUID et cela évitera le message d'erreur.

1
répondu Michi Salazar 2015-12-02 19:34:21

Juste si quelqu'un tombe sur ce vieux sujet. J'ai résolu le problème en modifiant d'abord le champ en un type CHAR, puis en un type UUID.

1
répondu pritstift 2017-09-19 08:38:06

Je me heurte à cela après un long moment, mais il existe un moyen de convertir votre colonne entière en un UUID avec une sorte de rétrocompatibilité, à savoir garder un moyen d'avoir une référence à vos anciennes valeurs, plutôt que de laisser tomber vos valeurs. Il comprend la conversion de votre valeur entière en une chaîne hexadécimale, puis le remplissage avec des zéros nécessaires pour constituer un UUID artificiel.

Donc, en supposant que votre colonne entière actuelle est nommée ColA, l'instruction suivante le ferait (attention au using partie):

ALTER TABLE tableA ALTER COLUMN ColA SET DATA TYPE UUID USING LPAD(TO_HEX(ColA), 32, '0')::UUID;
1
répondu JChrist 2018-03-22 12:48:07