Додавання "послідовного" до існуючого стовпця у Postgres


91

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

Як я можу змінити цю таблицю таким чином, щоб відтепер вставки до цієї таблиці спричиняли поведінку цього поля так, ніби воно було створене із типом "serial"?


5
FYI, SERIALпсевдо-тип тепер застарілий , витіснений новою GENERATED … AS IDENTITYфункцією, визначеною в SQL: 2003 , у Postgres 10 і пізніших версіях. Див. Пояснення .
Василь Бурк

Для сучасної версії Postgres (> = 10) см це питання: stackoverflow.com/questions/2944499
a_horse_with_no_name

Відповіді:


132

Перегляньте наступні команди (особливо коментований блок).

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).
Skippy le Grand Gourou

СЕРІАЛ є синтаксичним цукром і не зберігається в метаданих БД, тому наведений вище код буде на 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

48

Ви також можете використовувати 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');

28

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надається в кінці цієї відповіді.


Неінтерактивне рішення

Просто додавши до двох інших відповідей, для тих з нас, кому потрібно створити ці Sequences за допомогою неінтерактивного сценарію , одночасно виправляючи, наприклад, базу даних Live-ish.

Тобто, коли ви не хочете 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'); 

Використання a 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

Великий відповідь, але майте на увазі , coalesce(max(a), 0))не працюватиме більшу частину часу, так як Ідентифікатори зазвичай починаються з 1. Більш правильно було бcoalesce(max(a), 1))
Amiko

1
Дякую @Amiko за коментар! setvalФункція фактично тільки встановлює поточний «останній використовується значення» для послідовності. Наступним доступним значенням (першим, яке буде фактично використано) буде ще одне! Використання setval(..., coalesce(max(a), 1))на порожньому стовпчику призведе до початку "з" 2(наступного доступного значення), як показано в документації .
ccjmne

1
@Amiko Ви маєте рацію, стверджуючи, що в моєму коді є проблема: це currvalніколи не повинно бути 0, навіть якщо це не буде відображено у фактичному наборі даних. Використовуючи форму трипараметричної setvalбуде більш підходящим: setval(..., coalesce(max(a), 0) + 1, false). Відповідь оновлено відповідно!
ccjmne

1
Погоджений, мені це зовсім не вистачало. Дякую за відповідь заощадила мій час.
Amiko
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.