Odpowiedzi:
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.
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 .
END; $$
jest błędem składniowym (Postgres 9.3), musiałem użyć END $$;
zamiast tego
EXCEPTION
) jest nieco bardziej ogólne i można je zastosować do zadań, które nie mają IF NOT EXISTS
składni - na przykład ALTER TABLE ... ADD CONSTRAINT
.
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;
ADD CONSTRAINT IF NOT EXISTS
jeszcze nie ma .
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 TRUE
po sukcesie, w przeciwnym razie FALSE
(kolumna już istnieje).
Podnosi wyjątek dla nieprawidłowej nazwy tabeli lub typu.
Można to zrobić za pomocą DO
instrukcji, ale DO
instrukcje nie mogą niczego zwrócić. A jeśli jest do wielokrotnego użytku, utworzyłbym funkcję.
Korzystać z rodzajów identyfikator obiektu regclass
i regtype
w _tbl
i _type
którym a) zapobiega wstrzyknięciu SQL i b) kontroli ważności zarówno bezpośrednio (możliwe najtańszy sposób). Nazwa kolumny _col
nadal musi zostać oczyszczona za EXECUTE
pomocą 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_catalog
zamiast information_schema
. Szczegółowe wyjaśnienie:
Bloki zawierające EXCEPTION
klauzulę, takie jak aktualnie zaakceptowana odpowiedź, są znacznie wolniejsze. Jest to ogólnie prostsze i szybsze. Dokumentacja:
Wskazówka:
EXCEPTION
wejście i wyjście bloku zawierającego klauzulę jest znacznie droższe niż blok bez niej. Dlatego nie używajEXCEPTION
bez potrzeby.
DO
instrukcji, jest niewielka modyfikacja do zaakceptowania DEFAULT
i to działało idealnie!
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
$$
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;
$$
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)');
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();
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 $$;
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.
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.
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 wCREATE INDEX
;) Dziękuję za odpowiedź,