Wie füge ich eine Spalte hinzu, wenn sie in PostgreSQL nicht vorhanden ist?

145

Die Frage ist einfach. Wie füge ich eine Spalte xzur Tabelle hinzu y, aber nur, wenn xkeine Spalte vorhanden ist? Ich habe hier nur eine Lösung gefunden , um zu überprüfen, ob eine Spalte vorhanden ist.

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

Antworten:

133

Hier ist eine kurze und süße Version mit der Anweisung "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;
$$

Sie können diese nicht als Parameter übergeben. Sie müssen die Variablen auf der Clientseite in der Zeichenfolge ersetzen. Dies ist jedoch eine eigenständige Abfrage, die nur dann eine Nachricht ausgibt, wenn die Spalte bereits vorhanden ist. Wenn dies nicht der Fall ist, wird eine hinzugefügt schlägt weiterhin bei anderen Fehlern fehl (z. B. bei einem ungültigen Datentyp).

Ich empfehle keine dieser Methoden, wenn es sich um zufällige Zeichenfolgen handelt, die aus externen Quellen stammen. Unabhängig davon, welche Methode Sie verwenden (cleint- oder serverseitige dynamische Zeichenfolgen, die als Abfragen ausgeführt werden), ist dies ein Rezept für eine Katastrophe, da Sie dadurch SQL-Injection-Angriffe ausführen können.

Matthew Wood
quelle
4
DO $$ BEGIN BEGIN CREATE INDEX type_idx ON table1 USING btree (type); EXCEPTION WHEN duplicate_table THEN RAISE NOTICE 'Index exists.'; END; END;$$;der gleiche Ansatz in CREATE INDEX;) Danke für Ihre Antwort,
Marioosh
Ich bin mir nicht sicher, warum das Starten des anonymen Codeblocks mit DO $$fehlschlägt. Ich habe versucht, DO $$;was auch fehlschlägt, bis ich gerade den Block gestartet habe, mit DO $$DECLARE r record;dem in einem Beispiel in den dev postgres-Dokumenten angegeben ist .
Nemesisfixx
9
Das Schließen mit END; $$ist ein Syntaxfehler (Postgres 9.3), den ich END $$;stattdessen verwenden musste
LightSystem
5
Guter Ansatz, aber warum die verschachtelten BEGIN / END-Blöcke? Es funktioniert gut mit einer einzigen Schicht für mich. Durch Hinzufügen eines Semikolons am Ende ($$;) wird die Anweisung für psql eindeutig.
Shane
1
Dieser Ansatz ( EXCEPTION) ist etwas allgemeiner und kann beispielsweise für Aufgaben ohne IF NOT EXISTSSyntax verwendet werden ALTER TABLE ... ADD CONSTRAINT.
Tomasz Gandor
389

Mit Postgres 9.6 kann dies mit der Option erfolgenif not exists

ALTER TABLE table_name ADD COLUMN IF NOT EXISTS column_name INTEGER;
ein Pferd ohne Name
quelle
4
Süss. Leider gibt es noch keine ADD CONSTRAINT IF NOT EXISTS.
Tomasz Gandor
4
Warum ist diese Antwort am Ende der Seite, es ist so viel besser als die anderen Optionen.
Ecksters
Nur aus Neugier: Verursacht dies eine Zugriffssperre für die Tabelle (und erfordert daher ein Wartungsfenster, wenn sie auf großen Tabellen in Produktionsdatenbanken ausgeführt wird)?
Hassan Baig
4
Ein Stapelüberlauf sollte wirklich das Ändern der akzeptierten Antwort unterstützen.
Henrik Sommerland
@HenrikSommerland: das ist erlaubt - aber nur von der Person, die die Frage gestellt hat.
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;

Anruf:

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

Gibt TRUEbei Erfolg zurück, sonst FALSE(Spalte existiert bereits).
Löst eine Ausnahme für einen ungültigen Tabellen- oder Typnamen aus.

Warum eine andere Version?

  • Dies könnte mit einer DOAnweisung erfolgen, aber DOAnweisungen können nichts zurückgeben. Und wenn es für den wiederholten Gebrauch ist, würde ich eine Funktion erstellen.

  • Ich verwende den Objekt - ID - Typen regclass und regtypefür _tblund _typewelche a) verhindert SQL - Injection und b) prüft Gültigkeit sowohl unmittelbar (billigste Art und Weise). Der Spaltenname _colmuss noch mit bereinigt EXECUTEwerden quote_ident(). Weitere Erklärungen in dieser verwandten Antwort:

  • format()erfordert Postgres 9.1+. Bei älteren Versionen manuell verketten:

    EXECUTE 'ALTER TABLE ' || _tbl || ' ADD COLUMN ' || quote_ident(_col) || ' ' || _type;
  • Sie können Ihren Tabellennamen schemaqualifizieren, müssen dies aber nicht.
    Sie können die Bezeichner im Funktionsaufruf in doppelte Anführungszeichen setzen, um Wörter in Groß- und Kleinschreibung und reservierte Wörter beizubehalten (aber Sie sollten sowieso nichts davon verwenden).

  • Ich frage pg_catalogstatt der information_schema. Ausführliche Erklärung:

  • Blöcke, die eine EXCEPTIONKlausel wie die aktuell akzeptierte Antwort enthalten, sind wesentlich langsamer. Dies ist im Allgemeinen einfacher und schneller. Die Dokumentation:

Tipp: Ein Block mit einer EXCEPTIONKlausel ist beim Ein- und Aussteigen erheblich teurer als ein Block ohne eine. Verwenden Sie es daher nicht EXCEPTIONohne Notwendigkeit.

Erwin Brandstetter
quelle
Ich mag deine Lösung besser als meine! Es ist besser, sicherer und schneller.
David S
Die Version von Postgres, mit der ich arbeiten muss, hat keine DOAussage, eine geringfügige Änderung, die akzeptiert werden muss, DEFAULTund dies hat perfekt funktioniert!
Renab
18

Die folgende Auswahlabfrage wird true/falsemit der EXISTS()Funktion zurückgegeben.

EXISTS () :
Das Argument von EXISTS ist eine beliebige SELECT-Anweisung oder Unterabfrage. Die Unterabfrage wird ausgewertet, um festzustellen, ob Zeilen zurückgegeben werden. Wenn mindestens eine Zeile zurückgegeben wird, ist das Ergebnis von EXISTS "true". Wenn die Unterabfrage keine Zeilen zurückgibt, ist das Ergebnis von EXISTS "false".

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

Verwenden Sie die folgende dynamische SQL-Anweisung, um Ihre Tabelle zu ändern

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.
quelle
2
Doppelte Tabellennamen und Spaltennamen können in mehreren Schemas vorhanden sein.
Mike Sherrill 'Cat Recall'
1
Nun, vielleicht möchten Sie Ihren Code neu schreiben, um Schemata zu berücksichtigen.
Mike Sherrill 'Cat Recall'
2

Für diejenigen, die Postgre 9.5+ verwenden (ich glaube, die meisten von Ihnen), gibt es eine recht einfache und saubere Lösung

ALTER TABLE if exists <tablename> add if not exists <columnname> <columntype>
Leon
quelle
1

Die folgende Funktion überprüft die Spalte, falls vorhanden, und gibt die entsprechende Nachricht zurück. Andernfalls wird die Spalte zur Tabelle hinzugefügt.

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
quelle
Kommt mir als sehr vernünftige Antwort vor, zumal DO kürzlich zu postgres hinzugefügt wurde
John Powell
1

Dies ist im Grunde die Lösung von Sola, aber nur ein bisschen aufgeräumt. Es ist anders genug, dass ich nicht nur seine Lösung "verbessern" wollte (außerdem finde ich das irgendwie unhöflich).

Der Hauptunterschied besteht darin, dass das EXECUTE-Format verwendet wird. Was ich für etwas sauberer halte, aber meiner Meinung nach bedeutet, dass Sie auf PostgresSQL 9.1 oder neuer sein müssen.

Dies wurde auf 9.1 getestet und funktioniert. Hinweis: Es wird ein Fehler ausgegeben, wenn das Schema / der Tabellenname / oder der Datentyp ungültig sind. Das könnte "behoben" werden, könnte aber in vielen Fällen das richtige Verhalten sein.

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';

Verwendung:

select add_column('public', 'foo', 'bar', 'varchar(30)');
David S.
quelle
0

Kann zu Migrationsskripten hinzugefügt werden, ruft die Funktion auf und löscht sie, wenn sie fertig ist.

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
quelle
0

In meinem Fall ist es aus Gründen der Erstellung für unsere Migrationsskripte etwas schwierig, verschiedene Schemas zu überschneiden.

Um dies zu umgehen, haben wir eine Ausnahme verwendet, die den Fehler nur abgefangen und ignoriert hat. Dies hatte auch den schönen Nebeneffekt, dass es viel einfacher anzusehen war.

Seien Sie jedoch vorsichtig, dass die anderen Lösungen ihre eigenen Vorteile haben, die diese Lösung wahrscheinlich überwiegen:

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
quelle
-1

Sie können dies folgendermaßen tun.

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

Die Spalte wird also gelöscht, wenn sie bereits vorhanden ist. Fügen Sie dann die Spalte einer bestimmten Tabelle hinzu.

parthivrshah
quelle
17
Was ist mit Datenverlust?
Aliaksei Ramanau
48
Sie können sich immer bei Ihren Kunden entschuldigen
konzo
Ich hatte gerade die Spalte hinzugefügt, daher war dies für mich sehr praktisch.
Noumenon
-4

Überprüfen Sie einfach, ob die Abfrage einen Spaltennamen zurückgegeben hat.

Wenn nicht, führen Sie Folgendes aus:

ALTER TABLE x ADD COLUMN y int;

Wo Sie etwas Nützliches für 'x' und 'y' setzen und natürlich einen geeigneten Datentyp, bei dem ich int verwendet habe.

Erwin Möller
quelle
In welcher Umgebung bist du? Haben Sie eine Skriptsprache zu Ihrem Vorschlag? Oder verwenden Sie PL / pgSQL? Führen Sie eine Sprache wie PHP / Java / etc aus?
Erwin Möller
Keine Skriptsprache. Ich muss dies nur in SQL tun . Ich habe eine Java-Anwendung, die bei der Eingabe ein SQL-Skript erhält und dieses Skript auf einer ausgewählten Datenbank ausführt.
Marioosh
2
Dann rate ich Ihnen, sich mit pl / pgsql zu befassen: postgresql.org/docs/9.1/static/plpgsql.html Erstellen Sie eine Funktion, die Spaltenname und Tabellenname als Argumente verwendet.
Erwin Möller