Mam małą tabelę (~ 30 wierszy) w mojej bazie danych Postgres 9.0 z polem typu integer ID (klucz podstawowy), które obecnie zawiera unikalne sekwencyjne liczby całkowite zaczynające się od 1, ale które nie zostało utworzone przy użyciu słowa kluczowego „serial”.
Jak mogę zmienić tę tabelę tak, aby od tej chwili wstawienia do niej powodowały, że to pole zachowywało się tak, jakby zostało utworzone z typem „serial”?
postgresql
nicolaskruchten
źródło
źródło
SERIAL
pseudo-typ jest teraz starszy , wyparty przez nowąGENERATED … AS IDENTITY
funkcję zdefiniowaną w SQL: 2003 , w Postgres 10 i nowszych. Zobacz wyjaśnienie .Odpowiedzi:
Przyjrzyj się następującym poleceniom (zwłaszcza blokowi z komentarzem).
DROP TABLE foo; DROP TABLE bar; CREATE TABLE foo (a int, b text); CREATE TABLE bar (a serial, b text); INSERT INTO foo (a, b) SELECT i, 'foo ' || i::text FROM generate_series(1, 5) i; INSERT INTO bar (b) SELECT 'bar ' || i::text FROM generate_series(1, 5) i; -- blocks of commands to turn foo into bar CREATE SEQUENCE foo_a_seq; ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq'); ALTER TABLE foo ALTER COLUMN a SET NOT NULL; ALTER SEQUENCE foo_a_seq OWNED BY foo.a; -- 8.2 or later SELECT MAX(a) FROM foo; SELECT setval('foo_a_seq', 5); -- replace 5 by SELECT MAX result INSERT INTO foo (b) VALUES('teste'); INSERT INTO bar (b) VALUES('teste'); SELECT * FROM foo; SELECT * FROM bar;
źródło
ALTER TABLE foo ADD PRIMARY KEY (a)
.ALTER TABLE foo OWNER TO current_user;
najpierw.MAX(a)+1
w setval?SELECT MAX(a)+1 FROM foo; SELECT setval('foo_a_seq', 6);
Możesz także użyć
START WITH
do rozpoczęcia sekwencji od określonego punktu, chociaż setval robi to samo, co w odpowiedzi Eulera, np.SELECT MAX(a) + 1 FROM foo; CREATE SEQUENCE foo_a_seq START WITH 12345; -- replace 12345 with max above ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq');
źródło
TL; DR
Oto wersja, w której nie potrzebujesz człowieka do odczytania wartości i wpisania jej samodzielnie.
CREATE SEQUENCE foo_a_seq OWNED BY foo.a; SELECT setval('foo_a_seq', coalesce(max(a), 0) + 1, false) FROM foo; ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq');
Inną opcją byłoby wykorzystanie
Function
udostępnionego na końcu tej odpowiedzi pliku wielokrotnego użytku .Rozwiązanie nieinteraktywne
Po prostu dodając do pozostałych dwóch odpowiedzi, dla tych z nas, którzy potrzebują mieć je
Sequence
utworzone za pomocą nieinteraktywnego skryptu , na przykład poprawiając dynamiczną bazę danych.Oznacza to, że nie chcesz
SELECT
ręcznie wpisywać wartości i wpisywać ją samodzielnie w kolejnejCREATE
instrukcji.W skrócie, można nie zrobić:
CREATE SEQUENCE foo_a_seq START WITH ( SELECT max(a) + 1 FROM foo );
... ponieważ
START [WITH]
klauzula inCREATE SEQUENCE
oczekuje wartości , a nie podzapytania.Jednak
setval()
tak! Zatem absolutnie w porządku:SELECT setval('foo_a_seq', max(a)) FROM foo;
Jeśli nie ma danych i nie (chcesz) o tym wiedzieć, użyj,
coalesce()
aby ustawić wartość domyślną:SELECT setval('foo_a_seq', coalesce(max(a), 0)) FROM foo; -- ^ ^ ^ -- defaults to: 0
Jednak ustawienie bieżącej wartości sekwencji na
0
jest niezdarne, jeśli nie nielegalne. Bardziej odpowiednie byłobyużycie trzyparametrowej formy
setval
:-- vvv SELECT setval('foo_a_seq', coalesce(max(a), 0) + 1, false) FROM foo; -- ^ ^ -- is_called
Ustawienie opcjonalnego trzeciego parametru
setval
nafalse
to uniemożliwi następnemunextval
przesunięcie sekwencji przed zwróceniem wartości, a zatem:- z tego wpisu w dokumentacji
W niepowiązanej notatce możesz również określić kolumnę będącą właścicielem
Sequence
bezpośrednio zCREATE
, nie musisz jej później zmieniać:CREATE SEQUENCE foo_a_seq OWNED BY foo.a;
W podsumowaniu:
CREATE SEQUENCE foo_a_seq OWNED BY foo.a; SELECT setval('foo_a_seq', coalesce(max(a), 0) + 1, false) FROM foo; ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq');
Używać
Function
Alternatywnie, jeśli planujesz to zrobić dla wielu kolumn, możesz zdecydować się na użycie rzeczywistego pliku
Function
.CREATE OR REPLACE FUNCTION make_into_serial(table_name TEXT, column_name TEXT) RETURNS INTEGER AS $$ DECLARE start_with INTEGER; sequence_name TEXT; BEGIN sequence_name := table_name || '_' || column_name || '_seq'; EXECUTE 'SELECT coalesce(max(' || column_name || '), 0) + 1 FROM ' || table_name INTO start_with; EXECUTE 'CREATE SEQUENCE ' || sequence_name || ' START WITH ' || start_with || ' OWNED BY ' || table_name || '.' || column_name; EXECUTE 'ALTER TABLE ' || table_name || ' ALTER COLUMN ' || column_name || ' SET DEFAULT nextVal(''' || sequence_name || ''')'; RETURN start_with; END; $$ LANGUAGE plpgsql VOLATILE;
Użyj go w ten sposób:
INSERT INTO foo (data) VALUES ('asdf'); -- ERROR: null value in column "a" violates not-null constraint SELECT make_into_serial('foo', 'a'); INSERT INTO foo (data) VALUES ('asdf'); -- OK: 1 row(s) affected
źródło
coalesce(max(a), 0))
że nie będzie działać przez większość czasu, ponieważ identyfikatory zwykle zaczynają się od 1. Bardziej poprawny sposób byłbycoalesce(max(a), 1))
setval
rzeczywistości funkcja ustawia tylko bieżącą „ostatnio używaną wartość” dla sekwencji. Następna dostępna wartość (pierwsza faktycznie używana) będzie jeszcze jedna! Użyciesetval(..., coalesce(max(a), 1))
na pustej kolumnie ustawiłoby ją na „rozpoczęcie” z2
(następną dostępną wartością), jak pokazano w dokumentacji .currval
nigdy nie powinno go mieć0
, nawet jeśli nie zostałoby to odzwierciedlone w rzeczywistym zbiorze danych. Za pomocą formularza trzy parametrusetval
byłaby bardziej odpowiednia:setval(..., coalesce(max(a), 0) + 1, false)
. Odpowiednio zaktualizowana odpowiedź!