web-dev-qa-db-fra.com

Postgres: ajouter une contrainte si elle n'existe pas déjà

Postgres a-t-il un moyen de dire ALTER TABLE foo ADD CONSTRAINT bar ... qui ignorera simplement la commande si la contrainte existe déjà, afin qu'elle ne déclenche pas d'erreur?

51
Paul A Jungwirth

Cela pourrait aider, même si cela peut être un peu un hack sale:

create or replace function create_constraint_if_not_exists (
    t_name text, c_name text, constraint_sql text
) 
returns void AS
$$
begin
    -- Look for our constraint
    if not exists (select constraint_name 
                   from information_schema.constraint_column_usage 
                   where table_name = t_name  and constraint_name = c_name) then
        execute constraint_sql;
    end if;
end;
$$ language 'plpgsql'

Appelez ensuite avec:

SELECT create_constraint_if_not_exists(
        'foo',
        'bar',
        'ALTER TABLE foo ADD CONSTRAINT bar CHECK (foobies < 100);')

Mise à jour:

Selon réponse de Webmut ci-dessous, suggérant:

ALTER TABLE foo DROP CONSTRAINT IF EXISTS bar;
ALTER TABLE foo ADD CONSTRAINT bar ...;

C'est probablement bien dans votre base de données de développement, ou là où vous savez que vous pouvez fermer les applications qui dépendent de cette base de données pour une fenêtre de maintenance.

Mais s'il s'agit d'un environnement de production 24h/24 et 7j/7, vous ne voulez pas vraiment laisser tomber les contraintes comme ça. Même pendant quelques millisecondes, il y a une courte fenêtre où vous n'appliquez plus votre contrainte, ce qui peut permettre aux valeurs errantes de passer. Cela peut avoir des conséquences inattendues entraînant des coûts commerciaux considérables à un moment donné.

32
Kev

Une solution possible consiste à simplement utiliser DROP IF EXISTS avant de créer la nouvelle contrainte.

ALTER TABLE foo DROP CONSTRAINT IF EXISTS bar;
ALTER TABLE foo ADD CONSTRAINT bar ...;

Semble plus facile que d'essayer d'interroger le schéma_information ou les catalogues, mais peut être lent sur les tables énormes car il recrée toujours la contrainte.

Edit 2015-07-13: Kev a souligné dans sa réponse que ma solution crée une courte fenêtre lorsque la contrainte n'existe pas et n'est pas appliquée. Bien que cela soit vrai, vous pouvez éviter une telle fenêtre assez facilement en encapsulant les deux instructions dans une transaction.

62
Webmut

Vous pouvez utiliser un gestionnaire d'exceptions à l'intérieur d'un bloc DO anonyme pour intercepter l'erreur d'objet en double.

DO $$
BEGIN

  BEGIN
    ALTER TABLE foo ADD CONSTRAINT bar ... ;
  EXCEPTION
    WHEN duplicate_object THEN RAISE NOTICE 'Table constraint foo.bar already exists';
  END;

END $$;

http://www.postgresql.org/docs/9.4/static/sql-do.htmlhttp://www.postgresql.org/docs/9.4/static/plpgsql- control-structures.htmlhttp://www.postgresql.org/docs/9.4/static/errcodes-appendix.html

18
Mike Stankavich

vous pouvez exécuter la requête sur pg_constraint une table pour trouver la contrainte existe ou non.

SELECT 1 FROM pg_constraint WHERE conname = 'constraint_name'"
9
Behnam

La création de contraintes peut être une opération coûteuse sur une table contenant beaucoup de données, je vous recommande donc de ne pas supprimer les contraintes uniquement pour les recréer immédiatement immédiatement après - vous ne voulez créer cette chose qu'une seule fois.

J'ai choisi de résoudre ce problème en utilisant un bloc de code anonyme, très similaire à Mike Stankavich, mais contrairement à Mike (qui détecte une erreur), je vérifie d'abord si la contrainte existe:

DO $$
BEGIN
    IF NOT EXISTS ( SELECT  constraint_schema
                ,       constraint_name 
                FROM    information_schema.check_constraints 
                WHERE   constraint_schema = 'myschema'
                  AND   constraint_name = 'myconstraintname'
              )
    THEN
        ALTER TABLE myschema.mytable ADD CONSTRAINT myconstraintname CHECK (column <= 100);
    END IF;
END$$; 
6
jamiet