Dodanie „serial” do istniejącej kolumny w Postgres

98

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”?

nicolaskruchten
źródło
5
FYI, SERIALpseudo-typ jest teraz starszy , wyparty przez nową GENERATED … AS IDENTITYfunkcję zdefiniowaną w SQL: 2003 , w Postgres 10 i nowszych. Zobacz wyjaśnienie .
Basil Bourque,
W przypadku współczesnej wersji Postgres (> = 10) zobacz to pytanie: stackoverflow.com/questions/2944499
a_horse_with_no_name

Odpowiedzi:

135

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;
Euler Taveira
źródło
Ponieważ wspominasz o kluczach podstawowych w swoim OP, możesz również chcieć ALTER TABLE foo ADD PRIMARY KEY (a).
Skippy le Grand Gourou
SERIAL jest cukrem składniowym i nie jest przechowywany w metadanych DB, więc powyższy kod byłby w 100% równoważny.
DKroot
Jeśli istnieje prawdopodobieństwo, że tabela docelowa została utworzona przez innego użytkownika, musisz to zrobić ALTER TABLE foo OWNER TO current_user;najpierw.
DKroot
2
Nie powinieneś ustawiać MAX(a)+1w setval? SELECT MAX(a)+1 FROM foo; SELECT setval('foo_a_seq', 6);
SunnyPro,
50

Możesz także użyć START WITHdo 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');
John Powell
źródło
31

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 Functionudostę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 Sequenceutworzone za pomocą nieinteraktywnego skryptu , na przykład poprawiając dynamiczną bazę danych.

Oznacza to, że nie chcesz SELECTręcznie wpisywać wartości i wpisywać ją samodzielnie w kolejnej CREATEinstrukcji.

W skrócie, można nie zrobić:

CREATE SEQUENCE foo_a_seq
    START WITH ( SELECT max(a) + 1 FROM foo );

... ponieważ START [WITH]klauzula in CREATE SEQUENCEoczekuje wartości , a nie podzapytania.

Uwaga: Jako zasada, która dotyczy wszystkich non-CRUD ( tj : niczego innego niż INSERT, SELECT, UPDATE, DELETE) sprawozdania w pgSQL AFAIK.

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 0jest niezdarne, jeśli nie nielegalne. Bardziej odpowiednie byłoby
użycie trzyparametrowej formy setval:

--                                             vvv
SELECT setval('foo_a_seq', coalesce(max(a), 0) + 1, false) FROM foo;
--                                                  ^   ^
--                                                is_called

Ustawienie opcjonalnego trzeciego parametru setvalna falseto uniemożliwi następnemu nextvalprzesunięcie sekwencji przed zwróceniem wartości, a zatem:

next nextvalzwróci dokładnie określoną wartość, a postęp sekwencji rozpocznie się od następującego nextval.

- z tego wpisu w dokumentacji

W niepowiązanej notatce możesz również określić kolumnę będącą właścicielem Sequencebezpośrednio z CREATE, 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
ccjmne
źródło
Świetna odpowiedź, ale pamiętaj, 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))
Amiko
1
Dzięki @Amiko za komentarz! W setvalrzeczywistoś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życie setval(..., coalesce(max(a), 1))na pustej kolumnie ustawiłoby ją na „rozpoczęcie” z 2(następną dostępną wartością), jak pokazano w dokumentacji .
ccjmne
1
@Amiko Masz rację, mówiąc, że w moim kodzie jest problem: currvalnigdy nie powinno go mieć 0, nawet jeśli nie zostałoby to odzwierciedlone w rzeczywistym zbiorze danych. Za pomocą formularza trzy parametru setvalbyłaby bardziej odpowiednia: setval(..., coalesce(max(a), 0) + 1, false). Odpowiednio zaktualizowana odpowiedź!
ccjmne
1
Zgoda, całkowicie za tym tęskniłem. Dzięki za odpowiedź zaoszczędziłem czas.
Amiko