Comment ajouter une colonne si elle n'existe pas sur PostgreSQL?

145

La question est simple. Comment ajouter une colonne xà une table y, mais uniquement lorsque la xcolonne n'existe pas? Je n'ai trouvé que la solution ici pour vérifier si la colonne existe.

SELECT column_name 
FROM information_schema.columns 
WHERE table_name='x' and column_name='y';
Marioosh
la source

Réponses:

133

Voici une version courte et douce utilisant l'instruction "DO":

DO $$ 
    BEGIN
        BEGIN
            ALTER TABLE <table_name> ADD COLUMN <column_name> <column_type>;
        EXCEPTION
            WHEN duplicate_column THEN RAISE NOTICE 'column <column_name> already exists in <table_name>.';
        END;
    END;
$$

Vous ne pouvez pas les passer en tant que paramètres, vous devrez effectuer une substitution de variable dans la chaîne côté client, mais il s'agit d'une requête autonome qui n'émet un message que si la colonne existe déjà, ajoute si ce n'est pas le cas et continuera d'échouer sur d'autres erreurs (comme un type de données invalide).

Je ne recommande aucune de ces méthodes s'il s'agit de chaînes aléatoires provenant de sources externes. Quelle que soit la méthode que vous utilisez (chaînes dynamiques côté clé ou côté serveur exécutées sous forme de requêtes), ce serait une recette pour le désastre car cela vous ouvre aux attaques par injection SQL.

Matthew Wood
la source
4
DO $$ BEGIN BEGIN CREATE INDEX type_idx ON table1 USING btree (type); EXCEPTION WHEN duplicate_table THEN RAISE NOTICE 'Index exists.'; END; END;$$;la même approche dans CREATE INDEX;) Merci pour votre réponse,
marioosh
Je ne sais pas pourquoi le démarrage du bloc de code anonyme DO $$échoue. J'ai essayé DO $$;ce qui échoue aussi, jusqu'à ce que je viens de démarrer le bloc avec DO $$DECLARE r record;lequel est donné un exemple sur les docs dev postgres .
nemesisfixx
9
La fermeture avec END; $$est une erreur de syntaxe (Postgres 9.3), j'ai dû utiliser à la END $$;place
LightSystem
5
Bonne approche, mais pourquoi les blocs BEGIN / END imbriqués? Cela fonctionne bien avec une seule couche pour moi. L'ajout d'un point-virgule à la fin ($$;) rend l'instruction sans ambiguïté pour psql.
Shane
1
Cette approche ( EXCEPTION) est un peu plus générale et peut être utilisée pour des tâches sans IF NOT EXISTSsyntaxe, par exemple ALTER TABLE ... ADD CONSTRAINT.
Tomasz Gandor
390

Avec Postgres 9.6, cela peut être fait en utilisant l'optionif not exists

ALTER TABLE table_name ADD COLUMN IF NOT EXISTS column_name INTEGER;
un cheval sans nom
la source
4
Doux. Malheureusement, il n'y en a pas ADD CONSTRAINT IF NOT EXISTSencore.
Tomasz Gandor
4
Pourquoi cette réponse au bas de la page est-elle tellement meilleure que les autres options.
Ecksters
Juste par curiosité: cela va-t-il provoquer un verrouillage d'accès sur la table (et donc nécessiter une fenêtre de maintenance lors de l'exécution sur d'énormes tables dans les bases de données de production)?
Hassan Baig
4
Le débordement de pile devrait vraiment prendre en charge la modification de la réponse acceptée.
Henrik Sommerland
@HenrikSommerland: qui est permis - mais seulement par la personne qui a posé la question.
a_horse_with_no_name
22
CREATE OR REPLACE function f_add_col(_tbl regclass, _col  text, _type regtype)
  RETURNS bool AS
$func$
BEGIN
   IF EXISTS (SELECT 1 FROM pg_attribute
              WHERE  attrelid = _tbl
              AND    attname = _col
              AND    NOT attisdropped) THEN
      RETURN FALSE;
   ELSE
      EXECUTE format('ALTER TABLE %s ADD COLUMN %I %s', _tbl, _col, _type);
      RETURN TRUE;
   END IF;
END
$func$  LANGUAGE plpgsql;

Appel:

SELECT f_add_col('public.kat', 'pfad1', 'int');

Retourne TRUEen cas de succès, sinon FALSE(la colonne existe déjà).
Déclenche une exception pour un nom de table ou de type non valide.

Pourquoi une autre version?

  • Cela peut être fait avec une DOinstruction, mais les DOinstructions ne peuvent rien renvoyer. Et si c'est pour une utilisation répétée, je créerais une fonction.

  • On utilise les types d'identificateur d'objet regclass et regtypepour , _tblet _typequi a) empêche contrôles injection SQL et b) la validité de la fois immédiatement ( le moins cher de manière possible). Le nom de la colonne _coldoit encore être nettoyé pour EXECUTEavec quote_ident(). Plus d'explications dans cette réponse connexe:

  • format()nécessite Postgres 9.1+. Pour les anciennes versions concaténer manuellement:

    EXECUTE 'ALTER TABLE ' || _tbl || ' ADD COLUMN ' || quote_ident(_col) || ' ' || _type;
  • Vous pouvez qualifier de schéma le nom de votre table, mais ce n'est pas obligatoire.
    Vous pouvez double-citer les identifiants dans l'appel de fonction pour préserver la casse du chameau et les mots réservés (mais vous ne devriez pas utiliser tout cela de toute façon).

  • Je demande à la pg_catalogplace du fichier information_schema. Explication détaillée:

  • Les blocs contenant une EXCEPTIONclause comme la réponse actuellement acceptée sont sensiblement plus lents. C'est généralement plus simple et plus rapide. La documentation:

Conseil: un bloc contenant une EXCEPTIONclause est beaucoup plus coûteux à entrer et à sortir qu'un bloc sans clause. Par conséquent, ne l'utilisez pas EXCEPTIONsans nécessité.

Erwin Brandstetter
la source
J'aime mieux ta solution que la mienne! C'est mieux, plus sûr et plus rapide.
David S du
La version de Postgres avec laquelle je dois travailler n'a pas la DOdéclaration, une légère modification à accepter DEFAULTet cela a parfaitement fonctionné!
renab
18

La requête de sélection suivante reviendra true/false, en utilisant la EXISTS()fonction.

EXISTS () :
l'argument de EXISTS est une instruction SELECT arbitraire ou une sous-requête. La sous-requête est évaluée pour déterminer si elle renvoie des lignes. S'il renvoie au moins une ligne, le résultat de EXISTS est "true"; si la sous-requête ne renvoie aucune ligne, le résultat de EXISTS est "false"

SELECT EXISTS(SELECT  column_name 
                FROM  information_schema.columns 
               WHERE  table_schema = 'public' 
                 AND  table_name = 'x' 
                 AND  column_name = 'y'); 

et utilisez l'instruction SQL dynamique suivante pour modifier votre table

DO
$$
BEGIN
IF NOT EXISTS (SELECT column_name 
                 FROM  information_schema.columns 
                WHERE  table_schema = 'public' 
                  AND  table_name = 'x' 
                  AND  column_name = 'y') THEN
ALTER TABLE x ADD COLUMN y int DEFAULT NULL;
ELSE
RAISE NOTICE 'Already exists';
END IF;
END
$$
Vivek S.
la source
2
Des noms de table et de colonne en double peuvent exister dans plusieurs schémas.
Mike Sherrill 'Cat Recall'
1
Eh bien, vous voudrez peut-être réécrire votre code pour tenir compte des schémas.
Mike Sherrill 'Cat Recall'
2

Pour ceux qui utilisent Postgre 9.5+ (je crois que la plupart d'entre vous le font), il existe une solution assez simple et propre

ALTER TABLE if exists <tablename> add if not exists <columnname> <columntype>
Léon
la source
1

la fonction ci-dessous vérifiera la colonne si elle existe, renvoie le message approprié sinon elle ajoutera la colonne à la table.

create or replace function addcol(schemaname varchar, tablename varchar, colname varchar, coltype varchar)
returns varchar 
language 'plpgsql'
as 
$$
declare 
    col_name varchar ;
begin 
      execute 'select column_name from information_schema.columns  where  table_schema = ' ||
      quote_literal(schemaname)||' and table_name='|| quote_literal(tablename) || '   and    column_name= '|| quote_literal(colname)    
      into   col_name ;   

      raise info  ' the val : % ', col_name;
      if(col_name is null ) then 
          col_name := colname;
          execute 'alter table ' ||schemaname|| '.'|| tablename || ' add column '|| colname || '  ' || coltype; 
      else
           col_name := colname ||' Already exist';
      end if;
return col_name;
end;
$$
solaimuruganv
la source
Me semble être une réponse très raisonnable, d'autant plus que DO est un ajout récent à postgres
John Powell
1

C'est essentiellement la solution de sola, mais juste un peu nettoyée. C'est assez différent que je ne voulais pas simplement "améliorer" sa solution (en plus, je pense que c'est grossier).

La principale différence est qu'il utilise le format EXECUTE. Ce qui, je pense, est un peu plus propre, mais je crois que cela signifie que vous devez être sur PostgresSQL 9.1 ou plus récent.

Cela a été testé sur 9.1 et fonctionne. Remarque: Cela générera une erreur si le schéma / nom_table / ou le type_données ne sont pas valides. Cela pourrait "être corrigé", mais pourrait être le comportement correct dans de nombreux cas.

CREATE OR REPLACE FUNCTION add_column(schema_name TEXT, table_name TEXT, 
column_name TEXT, data_type TEXT)
RETURNS BOOLEAN
AS
$BODY$
DECLARE
  _tmp text;
BEGIN

  EXECUTE format('SELECT COLUMN_NAME FROM information_schema.columns WHERE 
    table_schema=%L
    AND table_name=%L
    AND column_name=%L', schema_name, table_name, column_name)
  INTO _tmp;

  IF _tmp IS NOT NULL THEN
    RAISE NOTICE 'Column % already exists in %.%', column_name, schema_name, table_name;
    RETURN FALSE;
  END IF;

  EXECUTE format('ALTER TABLE %I.%I ADD COLUMN %I %s;', schema_name, table_name, column_name, data_type);

  RAISE NOTICE 'Column % added to %.%', column_name, schema_name, table_name;

  RETURN TRUE;
END;
$BODY$
LANGUAGE 'plpgsql';

usage:

select add_column('public', 'foo', 'bar', 'varchar(30)');
David S
la source
0

Peut être ajouté aux scripts de migration, invoquer la fonction et supprimer une fois terminé.

create or replace function patch_column() returns void as
$$
begin
    if exists (
        select * from information_schema.columns
            where table_name='my_table'
            and column_name='missing_col'
     )
    then
        raise notice 'missing_col already exists';
    else
        alter table my_table
            add column missing_col varchar;
    end if;
end;
$$ language plpgsql;

select patch_column();

drop function if exists patch_column();
user645527
la source
0

Dans mon cas, pour la manière dont il a été créé, il est un peu difficile pour nos scripts de migration de traverser différents schémas.

Pour contourner ce problème, nous avons utilisé une exception qui vient de capturer et ignorer l'erreur. Cela a également eu le bel effet secondaire d'être beaucoup plus facile à regarder.

Cependant, sachez que les autres solutions ont leurs propres avantages qui l'emportent probablement sur cette solution:

DO $$
BEGIN
  BEGIN
    ALTER TABLE IF EXISTS bobby_tables RENAME COLUMN "dckx" TO "xkcd";
  EXCEPTION
    WHEN undefined_column THEN RAISE NOTICE 'Column was already renamed';
  END;
END $$;
ThinkBonobo
la source
-1

Vous pouvez le faire de la manière suivante.

ALTER TABLE tableName drop column if exists columnName; 
ALTER TABLE tableName ADD COLUMN columnName character varying(8);

Donc, il supprimera la colonne si elle existe déjà. Et puis ajoutez la colonne à une table particulière.

parthivrshah
la source
17
qu'en est-il de perdre des données?
Aliaksei Ramanau
48
Vous pouvez toujours vous excuser auprès de vos clients
konzo
Je venais d'ajouter la colonne, donc c'était très pratique pour moi.
Noumenon
-4

Vérifiez simplement si la requête a renvoyé un nom_colonne.

Sinon, exécutez quelque chose comme ceci:

ALTER TABLE x ADD COLUMN y int;

Où vous mettez quelque chose d'utile pour 'x' et 'y' et bien sûr un type de données approprié où j'ai utilisé int.

Erwin Moller
la source
Dans quel environnement êtes-vous? Avez-vous un langage de script à votre proposition? Ou utilisez-vous PL / pgSQL? Exécutez-vous à partir d'un langage tel que PHP / Java / etc?
Erwin Moller
Pas de langage de script. Je dois le faire uniquement dans SQL . J'ai une application Java qui, en entrée, obtient un script SQL et exécute ce script sur la base de données sélectionnée.
marioosh
2
Ensuite, je vous conseille de regarder dans pl / pgsql: postgresql.org/docs/9.1/static/plpgsql.html Créez une fonction qui prend nom_colonne et nom_table comme arguments.
Erwin Moller le