Postgres: Fügen Sie eine Einschränkung hinzu, falls diese noch nicht vorhanden ist

73

Hat Postgres eine Möglichkeit zu sagen, ALTER TABLE foo ADD CONSTRAINT bar ...welche den Befehl einfach ignoriert, wenn die Einschränkung bereits vorhanden ist, damit kein Fehler ausgelöst wird?

Paul A Jungwirth
quelle

Antworten:

47

Dies könnte helfen, obwohl es ein bisschen schmutzig sein kann:

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'

Dann rufen Sie an mit:

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

Aktualisiert:

Wie pro Webmut Antwort unten darauf hindeutet:

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

Das ist wahrscheinlich in Ihrer Entwicklungsdatenbank in Ordnung, oder wenn Sie wissen, dass Sie die von dieser Datenbank abhängigen Apps für ein Wartungsfenster schließen können.

Aber wenn dies eine lebhafte, geschäftskritische Produktionsumgebung ist, die rund um die Uhr verfügbar ist, möchten Sie Einschränkungen nicht so oder so fallen lassen. Selbst für einige Millisekunden gibt es ein kurzes Fenster, in dem Sie Ihre Einschränkung nicht mehr erzwingen, wodurch möglicherweise fehlerhafte Werte durchgehen. Dies kann unbeabsichtigte Folgen haben und irgendwann zu erheblichen Geschäftskosten führen.

Kev
quelle
Ich denke, das 'myconstraint'sollte 'bar'in Ihrem letzten Beispiel sein.
Denis de Bernardy
2
Ich würde diese Antwort weiter modifizieren, so dass die execute-Anweisung lautet execute 'ALTER TABLE ' || t_name || ' ADD CONSTRAINT ' || c_name || ' ' || constraint_sql;und der Aufruf der Funktion dann so aussehen würde SELECT create_constraint_if_not_exists('foo', 'bar', 'CHECK (foobies < 100);');. Dies stellt sicher, dass Sie die Argumente in Ihrer Einschränkungs-SQL nicht durcheinander bringen können, da sie auf den ursprünglichen Parametern basieren.
Tim Mattison
Ich stimme @TimMattison voll und ganz zu, gehe aber noch einen Schritt weiter und füge `|| hinzu ';' 'am Ende des executeSatzes (vorher ;), damit ich das Nachfolgen ;des Arguments beim Aufrufen der Funktion beseitigen kann .
Masa Sakano
4
Das kurze Fenster wird mithilfe einer Transaktion entfernt.
OrangeDog
Dies würde keine Fremdschlüsseleinschränkung finden, aber die Auswahl aus information_schema.table_constraintstut dies.
iElectric
89

Eine mögliche Lösung besteht darin, einfach DROP IF EXISTS zu verwenden, bevor die neue Einschränkung erstellt wird.

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

Scheint einfacher zu sein als der Versuch, information_schema oder Kataloge abzufragen, kann jedoch bei großen Tabellen langsam sein, da die Einschränkung immer neu erstellt wird.

Bearbeiten 2015-07-13: Kev wies in seiner Antwort darauf hin, dass meine Lösung ein kurzes Fenster erstellt, wenn die Einschränkung nicht existiert und nicht durchgesetzt wird. Während dies zutrifft, können Sie ein solches Fenster ganz einfach vermeiden, indem Sie beide Anweisungen in eine Transaktion einschließen.

Webmut
quelle
5
Ich weiß, dass dieser Thread alt ist, aber es lohnt sich vielleicht darauf hinzuweisen, dass Transaktionen normalerweise für DML gelten und Befehle zum Ändern von Tabellen DDL sind. DDL-Befehle können normalerweise nicht zurückgesetzt werden (abhängig von der Datenbank). Allerdings unterstützt Postgres das Zurücksetzen von DDL, Oracle und MySQL nicht. In diesem Thread finden Sie DB-spezifische Details: stackoverflow.com/questions/4692690/…
MattW
2
Ich würde empfehlen, keinen Code zu schreiben, der Einschränkungen aufhebt, um sie neu zu erstellen, da Webmut darauf hinweist, dass dies bei großen Tabellen langsam sein kann. Gehen Sie kein Risiko ein, sondern prüfen Sie zunächst, ob die Einschränkung besteht. Ich zeige in einer anderen Antwort auf diese Frage, wie.
Jamiet
Sie werden auf Probleme stoßen, wenn die Anweisung wiederholbar sein muss. Beispiel: Sie erstellen zuerst die Einschränkung A wie folgt und dann die Einschränkung B, die irgendwie von der Einschränkung A abhängt. Anschließend müssen Sie die Anweisung, die die Erstellung der Einschränkung A enthält, erneut ausführen. Es wird ein Fehler ausgegeben: A kann nicht gelöscht werden, weil B. hängt davon ab.
cis
24

Sie können einen Ausnahmebehandler in einem anonymen DO-Block verwenden, um den doppelten Objektfehler abzufangen.

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.html http://www.postgresql.org/docs/9.4/static/plpgsql-control-structures.html http: // www. postgresql.org/docs/9.4/static/errcodes-appendix.html

Mike Stankavich
quelle
3
Musste wechseln duplicate_objectzu duplicate_table(Code 42P07). Postgres 9.6
volvpavl
Postgres 10.1 hat es zurück zu duplicate_object.
Tanktalus
2
duplicate_objectfür FOREIGN KEY. duplicate_tablefür UNIQUEEinschränkungen. Postgres 9.6.8
dfritch
11

Sie können eine Abfrage über eine pg_constraintTabelle ausführen, um festzustellen, ob eine Einschränkung vorhanden ist oder nicht.

SELECT 1 FROM pg_constraint WHERE conname = 'constraint_name'"
Behnam
quelle
4
Es gibt eine IF EXISTSOption für, DROP CONSTRAINTaber, AFAIK, nichts für ADD CONSTRAINT.
Mu ist zu kurz
1
Sind Einschränkungsnamen lokal für eine Tabelle? Was passiert, wenn zwei Tabellen mit einer Einschränkung benannt sind constraint_name?
Guettli
Ja, Sie könnten falsch positive Ergebnisse erhalten, wenn eine andere Tabelle denselben Einschränkungsnamen hätte. Dies ist jedoch immer noch eine anständige Lösung, wenn Sie 100% Kontrolle über Ihre Benennung haben.
Joe M
10

Das Erstellen von Einschränkungen kann eine teure Operation für eine Tabelle mit vielen Daten sein. Ich empfehle daher, Einschränkungen nicht nur zu löschen, um sie sofort danach erneut zu erstellen. Sie möchten das Objekt nur einmal erstellen.

Ich habe mich entschieden, dies mit einem anonymen Codeblock zu lösen, der Mike Stankavich sehr ähnlich ist. Im Gegensatz zu Mike (der einen Fehler abfängt) überprüfe ich jedoch zuerst, ob die Einschränkung besteht:

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$$; 
Jamiet
quelle
@All - In meinem Fall werden Einschränkungen von JPA erstellt und diese Namen sind nicht vorhanden. Constraint_schema
Pra_A
2
Ich musste information_schema.constraint_column_usagestattdessen verwenden, was mir die Kontrolle über die Namen gab, die ich lesen konnte, sowie über den Tabellennamen.
CTS_AE
-2

In Anbetracht aller oben genannten Antworten hilft der folgende Ansatz, wenn Sie nur überprüfen möchten, ob in der Tabelle, in die Sie einfügen möchten, eine Einschränkung vorhanden ist, und gegebenenfalls eine Benachrichtigung auslösen möchten

DO 
$$ BEGIN
IF NOT EXISTS (select constraint_name 
               from information_schema.table_constraints 
               where table_schema='schame_name' and upper(table_name) = 
upper('table_name')  and upper(constraint_name) = upper('constraint_name'))

THEN

   ALTER TABLE TABLE_NAME ADD CONSTRAINT CONTRAINT_NAME..... ;

ELSE raise NOTICE 'Constraint CONTRAINT_NAME already exists in Table TABLE_NAME';   

END IF;
END
$$;
charle819
quelle
Sagen Sie bitte, was der Grund für die Ablehnung war.
Charles 819
1
Ich denke, der Grund, warum es abgelehnt wurde, war, dass es genau wie Jamiets Antwort aussieht. Bereits vorhandenen Antworten wird nichts Wertvolles hinzugefügt.
Sauerampfer Vesper
-4

Sie wissen nicht, warum so viele Codezeilen?

- SELECT "Column1", "Column2", "Column3", count (star) FROM dbo. "MyTable" GROUP BY "Column1", "Column2", "Column3" HAVING count (*)> 1;

alter table dbo. "MyTable" Drop-Einschränkung, falls vorhanden "MyConstraint_Name";

ALTER TABLE dbo. "MyTable" ADD CONSTRAINT "MyConstraint_Name" UNIQUE ("Column1", "Column3", "Column2");

user3121618
quelle