Добавление серийного номера в существующий столбец в Postgres

98

У меня есть небольшая таблица (~ 30 строк) в моей базе данных Postgres 9.0 с полем целочисленного идентификатора (первичный ключ), которая в настоящее время содержит уникальные последовательные целые числа, начинающиеся с 1, но которая не была создана с использованием ключевого слова serial.

Как я могу изменить эту таблицу так, чтобы с этого момента вставки в эту таблицу заставляли это поле вести себя так, как если бы оно было создано с типом «серийный»?

Nicolaskruchten
источник
5
К вашему сведению, SERIALпсевдотип теперь унаследован , заменен новой GENERATED … AS IDENTITYфункцией, определенной в SQL: 2003 , в Postgres 10 и более поздних версиях. См. Объяснение .
Basil
Для современной версии Postgres (> = 10) см. Этот вопрос: stackoverflow.com/questions/2944499
a_horse_with_no_name

Ответы:

135

Посмотрите на следующие команды (особенно на закомментированный блок).

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;
Эйлер Тавейра
источник
Поскольку вы упоминаете первичные ключи в своем OP, вы также можете захотеть ALTER TABLE foo ADD PRIMARY KEY (a).
Скиппи ле Гран Гуру
SERIAL - это синтаксический сахар и не хранится в метаданных БД, поэтому приведенный выше код будет на 100% эквивалентным.
DKroot
Если есть вероятность, что целевая таблица была создана другим пользователем, вам нужно сделать это в ALTER TABLE foo OWNER TO current_user;первую очередь.
DKroot
2
MAX(a)+1Разве вы не должны установить значение setval? SELECT MAX(a)+1 FROM foo; SELECT setval('foo_a_seq', 6);
SunnyPro
50

Вы также можете использовать START WITHдля запуска последовательности с определенной точки, хотя setval выполняет то же самое, что и в ответе Эйлера, например,

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');
Джон Пауэлл
источник
31

TL; DR

Вот версия, в которой вам не нужен человек, который сам считывает значение и набирает его.

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

Другой вариант - использовать повторно используемый Functionобщий ресурс в конце этого ответа.


Неинтерактивное решение

Просто добавив к двум другим ответам, для тех из нас, кому нужно, чтобы они были Sequenceсозданы неинтерактивным сценарием , например, при исправлении живой БД.

То есть, когда вы не хотите SELECTвручную вводить значение и вводить его самостоятельно в следующем CREATEоператоре.

Короче говоря, вы можете не делать:

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

... поскольку START [WITH]предложение in CREATE SEQUENCEожидает значение , а не подзапрос.

Примечание: Как правило, это относится ко всем без CRUD ( то есть : ничего, кроме INSERT, SELECT, UPDATE, DELETE) отчетности в PGSQL AFAIK.

Однако setval()делает! Таким образом, абсолютно нормально:

SELECT setval('foo_a_seq', max(a)) FROM foo;

Если данных нет и вы не (хотите) знать об этом, используйте coalesce()для установки значения по умолчанию:

SELECT setval('foo_a_seq', coalesce(max(a), 0)) FROM foo;
--                         ^      ^         ^
--                       defaults to:       0

Однако установка текущего значения последовательности в значение 0неуклюже, если не незаконно.
Используя форму трехпараметрической setvalбы более целесообразно:

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

Установка необязательного третьего параметра setvalto falseпредотвратит nextvalпродвижение следующей последовательности перед возвратом значения, и, таким образом:

следующий nextvalвернет точно указанное значение, и продвижение последовательности начнется со следующего nextval.

- из этой записи в документации

В несвязанной заметке вы также можете указать столбец, которому принадлежит Sequenceнепосредственно CREATE, вам не нужно изменять его позже:

CREATE SEQUENCE foo_a_seq OWNED BY foo.a;

В итоге:

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

Используя Function

В качестве альтернативы, если вы планируете сделать это для нескольких столбцов, вы можете выбрать фактический 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;

Используйте это так:

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
источник
Отличный ответ, но имейте в виду, coalesce(max(a), 0))что не будет работать в большинстве случаев, поскольку идентификаторы обычно начинаются с 1. Более правильный способcoalesce(max(a), 1))
Амико
1
Спасибо @Amiko за комментарий! setvalФункция фактически только устанавливает текущее «последний используется значение» для последовательности. Следующее доступное значение (первое, которое будет фактически использовано) будет на одно больше! Использование setval(..., coalesce(max(a), 1))в пустом столбце установит для него "начало" 2(следующее доступное значение), как показано в документации .
ccjmne
1
@Amiko Вы правы, говоря, что в моем коде есть проблема: currvalее никогда не должно быть 0, даже если она не будет отражена в фактическом наборе данных. Используя форму трехпараметрическую setvalбудет более подходящим: setval(..., coalesce(max(a), 0) + 1, false). Ответ обновлен соответственно!
ccjmne
1
Согласен, я полностью упустил это. Спасибо за ответ сэкономили мне время.
Амико