Jak dodać kolumnę, jeśli nie istnieje w PostgreSQL?

145

Pytanie jest proste. Jak dodać kolumnę xdo tabeli y, ale tylko wtedy, gdy xkolumna nie istnieje? Znalazłem tutaj jedyne rozwiązanie jak sprawdzić czy kolumna istnieje.

SELECT column_name 
FROM information_schema.columns 
WHERE table_name='x' and column_name='y';
marioosh
źródło

Odpowiedzi:

133

Oto krótka i przyjemna wersja wyrażenia „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;
$$

Nie możesz przekazać ich jako parametrów, musisz dokonać podstawienia zmiennych w ciągu znaków po stronie klienta, ale jest to samoistne zapytanie, które wysyła wiadomość tylko wtedy, gdy kolumna już istnieje, dodaje, jeśli nie, i będzie nadal kończyć się niepowodzeniem w przypadku innych błędów (takich jak nieprawidłowy typ danych).

Nie polecam wykonywania ŻADNEJ z tych metod, jeśli są to losowe ciągi pochodzące ze źródeł zewnętrznych. Bez względu na to, jakiej metody użyjesz (ciągi dynamiczne po stronie cleinta lub po stronie serwera, wykonywane jako zapytania), byłaby to recepta na katastrofę, ponieważ otwiera cię na ataki typu SQL injection.

Matthew Wood
źródło
4
DO $$ BEGIN BEGIN CREATE INDEX type_idx ON table1 USING btree (type); EXCEPTION WHEN duplicate_table THEN RAISE NOTICE 'Index exists.'; END; END;$$;to samo podejście w CREATE INDEX;) Dziękuję za odpowiedź,
marioosh
Nie jestem pewien, dlaczego po prostu uruchomienie bloku anonimowego kodu DO $$kończy się niepowodzeniem. Próbowałem DO $$;, ale to też się nie powiodło, dopóki nie zacząłem bloku, DO $$DECLARE r record;który jest podany w przykładzie w dokumentach dev postgres .
nemesisfixx
9
Zamykanie za pomocą END; $$jest błędem składniowym (Postgres 9.3), musiałem użyć END $$;zamiast tego
LightSystem
5
Dobre podejście, ale po co zagnieżdżone bloki BEGIN / END? U mnie działa dobrze z jedną warstwą. Dodanie średnika na końcu ($$;) czyni instrukcję jednoznaczną dla psql.
Shane,
1
To podejście ( EXCEPTION) jest nieco bardziej ogólne i można je zastosować do zadań, które nie mają IF NOT EXISTSskładni - na przykład ALTER TABLE ... ADD CONSTRAINT.
Tomasz Gandor
389

W przypadku Postgres 9.6 można to zrobić za pomocą opcjiif not exists

ALTER TABLE table_name ADD COLUMN IF NOT EXISTS column_name INTEGER;
koń bez imienia
źródło
4
Słodkie. Niestety ADD CONSTRAINT IF NOT EXISTSjeszcze nie ma .
Tomasz Gandor
4
Dlaczego ta odpowiedź jest na dole strony, jest tak dużo lepsza niż inne opcje.
Ecksters
Tak z ciekawości: czy spowoduje to blokadę dostępu do tabeli (a tym samym będzie wymagało okna obsługi podczas uruchamiania na dużych tabelach w produkcyjnych bazach danych)?
Hassan Baig
4
Przepełnienie stosu naprawdę powinno wspierać zmianę zaakceptowanej odpowiedzi.
Henrik Sommerland
@HenrikSommerland: to jest dozwolone - ale tylko przez osobę, która zadała pytanie.
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;

Połączenie:

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

Zwraca TRUEpo sukcesie, w przeciwnym razie FALSE(kolumna już istnieje).
Podnosi wyjątek dla nieprawidłowej nazwy tabeli lub typu.

Dlaczego inna wersja?

  • Można to zrobić za pomocą DOinstrukcji, ale DOinstrukcje nie mogą niczego zwrócić. A jeśli jest do wielokrotnego użytku, utworzyłbym funkcję.

  • Korzystać z rodzajów identyfikator obiektu regclass i regtypew _tbli _typektórym a) zapobiega wstrzyknięciu SQL i b) kontroli ważności zarówno bezpośrednio (możliwe najtańszy sposób). Nazwa kolumny _colnadal musi zostać oczyszczona za EXECUTEpomocą quote_ident(). Więcej wyjaśnień w tej powiązanej odpowiedzi:

  • format()wymaga Postgres 9.1+. W przypadku starszych wersji połącz ręcznie:

    EXECUTE 'ALTER TABLE ' || _tbl || ' ADD COLUMN ' || quote_ident(_col) || ' ' || _type;
  • Możesz zakwalifikować nazwę swojej tabeli do schematu, ale nie musisz.
    Identyfikatory w wywołaniu funkcji można umieszczać w cudzysłowach, aby zachować wielkość liter i słowa zastrzeżone (ale i tak nie należy ich używać).

  • Pytam pg_catalogzamiast information_schema. Szczegółowe wyjaśnienie:

  • Bloki zawierające EXCEPTIONklauzulę, takie jak aktualnie zaakceptowana odpowiedź, są znacznie wolniejsze. Jest to ogólnie prostsze i szybsze. Dokumentacja:

Wskazówka: EXCEPTIONwejście i wyjście bloku zawierającego klauzulę jest znacznie droższe niż blok bez niej. Dlatego nie używaj EXCEPTIONbez potrzeby.

Erwin Brandstetter
źródło
Twoje rozwiązanie podoba mi się bardziej niż moje! Jest lepiej, bezpieczniej i szybciej.
David S
Wersja Postgresa, z którą muszę pracować, nie ma DOinstrukcji, jest niewielka modyfikacja do zaakceptowania DEFAULTi to działało idealnie!
renab
18

Następujące zapytanie wyboru zwróci true/false, używając EXISTS()funkcji.

EXISTS () :
argumentem EXISTS jest dowolna instrukcja SELECT lub podzapytanie. Podzapytanie jest oceniane w celu określenia, czy zwraca jakiekolwiek wiersze. Jeśli zwraca co najmniej jeden wiersz, wynikiem EXISTS jest „prawda”; jeśli podzapytanie nie zwróci żadnych wierszy, wynikiem EXISTS jest „fałsz”

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

i użyj poniższej dynamicznej instrukcji SQL, aby zmienić tabelę

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.
źródło
2
W wielu schematach mogą istnieć zduplikowane nazwy tabel i nazw kolumn.
Mike Sherrill `` Cat Recall ''
1
Cóż, możesz chcieć przepisać swój kod, aby uwzględnić schematy.
Mike Sherrill `` Cat Recall ''
2

Dla tych, którzy używają Postgre 9.5+ (wierzę, że większość z was to robi), istnieje dość proste i czyste rozwiązanie

ALTER TABLE if exists <tablename> add if not exists <columnname> <columntype>
Leon
źródło
1

poniższa funkcja sprawdzi kolumnę, jeśli istnieje, zwróci odpowiednią wiadomość, w przeciwnym razie doda kolumnę do tabeli.

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
źródło
Wydaje mi się, że to bardzo rozsądna odpowiedź, zwłaszcza że DO jest niedawnym dodatkiem do postgres
John Powell
1

To jest w zasadzie rozwiązanie z Sola, ale tylko trochę wyczyszczone. Jest na tyle inny, że nie chciałem tylko „ulepszać” jego rozwiązania (plus, wydaje mi się, że to niegrzeczne).

Główną różnicą jest to, że używa formatu EXECUTE. Myślę, że jest to trochę czystsze, ale moim zdaniem oznacza, że ​​musisz być na PostgresSQL 9.1 lub nowszym.

Zostało to przetestowane w wersji 9.1 i działa. Uwaga: zgłosi błąd, jeśli schemat / nazwa_tabeli / lub typ_danych są nieprawidłowe. To mogłoby „naprawić”, ale w wielu przypadkach może być poprawnym zachowaniem.

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

stosowanie:

select add_column('public', 'foo', 'bar', 'varchar(30)');
David S.
źródło
0

Można dodać do migracji skrypty wywołują funkcję i upuszczają po zakończeniu.

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
źródło
0

W moim przypadku, ze względu na to, jak został stworzony, naszym skryptom migracji jest trochę trudno przeciąć różne schematy.

Aby obejść ten problem, użyliśmy wyjątku, który właśnie przechwycił i zignorował błąd. Miało to również przyjemny efekt uboczny polegający na tym, że był o wiele łatwiejszy do obejrzenia.

Należy jednak uważać, aby inne rozwiązania miały swoje zalety, które prawdopodobnie przeważają nad tym rozwiązaniem:

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
źródło
-1

Możesz to zrobić postępując zgodnie z instrukcją.

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

Więc usunie kolumnę, jeśli już istnieje. A następnie dodaj kolumnę do konkretnej tabeli.

parthivrshah
źródło
17
a co z utratą danych?
Aliaksei Ramanau
48
Zawsze możesz przeprosić swoich klientów
konzo
Właśnie dodałem kolumnę, więc było to dla mnie bardzo wygodne.
Noumenon
-4

Po prostu sprawdź, czy zapytanie zwróciło nazwę_kolumny.

Jeśli nie, wykonaj coś takiego:

ALTER TABLE x ADD COLUMN y int;

Gdzie umieścisz coś przydatnego dla 'x' i 'y' i oczywiście odpowiedni typ danych, w którym użyłem int.

Erwin Moller
źródło
W jakim środowisku jesteś? Czy masz propozycję języka skryptowego? A może używasz PL / pgSQL? Czy wykonujesz z jakiegoś języka, takiego jak PHP / Java / etc?
Erwin Moller
Brak języka skryptowego. Muszę to zrobić tylko w SQL . Mam aplikację Java, która na wejściu pobiera skrypt SQL i uruchamia ten skrypt na wybranej bazie danych.
marioosh,
2
Następnie radzę zajrzeć do pl / pgsql: postgresql.org/docs/9.1/static/plpgsql.html Utwórz funkcję, która przyjmuje jako argumenty nazwa_kolumny i nazwa_tabeli.
Erwin Moller,