Ручные вставки в таблицу postgres с последовательностью первичного ключа - PullRequest
12 голосов
/ 11 октября 2010

Я впервые в жизни преобразовываю таблицу MySQL в PostgreSQL и сталкиваюсь с традиционной для новичка проблемой отсутствия auto_increment.

Теперь я обнаружил, что решение postgres состоит в том, чтобы использовать последовательность и затем запрашивать nextval () этой последовательности в качестве значения по умолчанию при каждой вставке. Я также читал, что тип SERIAL автоматически создает последовательность и первичный ключ и что nextval () увеличивает счетчик даже при вызове внутри транзакций, чтобы избежать блокировки последовательности.

Что я не могу найти, так это то, что происходит, когда вы вручную вставляете значения в поле с ограничением UNIQUE или PRIMARY и nextval () последовательности по умолчанию. Насколько я вижу, это вызывает сбой INSERT, когда последовательность достигает этого значения.

Есть ли простой (или распространенный) способ исправить это?

Я бы очень хотел получить четкое объяснение.

Обновление: если вы считаете, что я не должен этого делать, никогда не сможете это исправить или я делаю некоторые ошибочные предположения, пожалуйста, не стесняйтесь указывать их в своих ответах. Прежде всего, скажите, пожалуйста, что нужно сделать, чтобы предложить программистам стабильную и надежную базу данных, которая не может быть повреждена простой вставкой (желательно, не скрывая все за хранимыми процедурами)

Ответы [ 5 ]

10 голосов
/ 11 октября 2010

Если вы переносите свои данные, тогда я бы снял ограничение последовательности в столбце, выполнил все ваши вставки, использовал setval () , чтобы установить последовательность на максимальное значение ваших данных, а затем восстановить последовательность столбцов nextval () по умолчанию.

4 голосов
/ 12 августа 2015

Чтобы расширить замечательный ответ Тометцки, вот более общая версия:

CREATE OR REPLACE FUNCTION check_serial() RETURNS trigger AS $$
BEGIN
  IF currval(TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME || '_' || TG_ARGV[0] || '_seq') <
    (row_to_json(NEW)->>TG_ARGV[0])::bigint
  THEN RAISE SQLSTATE '55000';  -- same as currval() of uninitialized sequence
  END IF;
  RETURN NULL;
EXCEPTION     
  WHEN SQLSTATE '55000'
  THEN RAISE 'manual entry of serial field %.%.% disallowed',
    TG_TABLE_SCHEMA, TG_TABLE_NAME, TG_ARGV[0]
    USING HINT = 'use DEFAULT instead of specifying value manually',
      SCHEMA = TG_TABLE_SCHEMA, TABLE = TG_TABLE_NAME, COLUMN = TG_ARGV[0];
END;
$$ LANGUAGE plpgsql;

Который вы можете применить к любому столбцу, скажем test.id, таким образом:

CREATE CONSTRAINT TRIGGER test_id_check
  AFTER INSERT OR UPDATE OF id ON test
  FOR EACH ROW EXECUTE PROCEDURE check_serial(id);
3 голосов
/ 13 октября 2010

Вы можете создать триггер, который будет проверять, если currval('id_sequence_name')>=NEW.id.

Если в вашей транзакции не использовалось значение по умолчанию или nextval('id_sequence_name'), функция currval выдаст ошибку, поскольку она работает только при обновлении последовательности в текущем сеансе. Если вы используете nextval, а затем попытаетесь вставить больший первичный ключ, это вызовет еще одну ошибку. Транзакция будет прервана.

Это предотвратит вставку любых плохих первичных ключей, которые могут сломать серийный номер.

Пример кода:

create table test (id serial primary key, value text);

create or replace function test_id_check() returns trigger language plpgsql as
$$ begin
  if ( currval('test_id_seq')<NEW.id ) then
    raise exception 'currval(test_id_seq)<id';
  end if;
  return NEW;
end; $$;

create trigger test_id_seq_check before insert or update of id on test
  for each row execute procedure test_id_check();

Тогда вставка с первичным ключом по умолчанию будет работать нормально:

insert into test(value) values ('a'),('b'),('c'),('d');

Но при вставке слишком большого первичного ключа произойдет ошибка и произойдет сбой:

insert into test(id, value) values (10,'z');
2 голосов
/ 11 октября 2010

Я не совсем понимаю ваш вопрос, но если ваша цель просто сделать вставку и иметь действительное поле (например, идентификатор), то вставьте значения без поля идентификатора, это то, что означает «по умолчанию»,Это будет работать.

Например, если * id serial NOT NULL и CONSTRAINT table_pkey PRIMARY KEY(id) в определении таблицы будут автоматически устанавливать идентификатор и автоматически увеличивать последовательность table_id_seq.

1 голос
/ 28 августа 2017

Как насчет использования CHECK?

CREATE SEQUENCE pk_test
  INCREMENT 1
  MINVALUE 1
  MAXVALUE 9223372036854775807
  START 1
  CACHE 1;

CREATE TABLE test (
    id INT PRIMARY KEY CHECK (id=currval('pk_test')) DEFAULT nextval('pk_test'),
    num int not null
    );
ALTER SEQUENCE pk_test OWNED BY test.id;

-- Testing:
INSERT INTO test (num) VALUES (3) RETURNING id, num;
1,3 -- OK
2,3 -- OK

INSERT INTO test (id, num) values (30,3) RETURNING id, num;
/*
ERROR:  new row for relation "test" violates check constraint "test_id_check"
DETAIL:  Failing row contains (30, 3).

********** Error **********

ERROR: new row for relation "test" violates check constraint "test_id_check"
SQL state: 23514
Detail: Failing row contains (30, 3).
*/

DROP TABLE test;
...