手动插入带有主键序列的postgres表

时间:2010-10-11 10:33:06

标签: postgresql primary-key sequence

我在生命中第一次将MySQL表转换为PostgreSQL,并遇到了没有auto_increment的传统新手问题。

现在我发现postgres解决方案是使用一个序列,然后在每次插入时请求此序列的nextval()作为默认值。我还读过SERIAL类型自动创建序列和主键,即使在事务内部调用时,nextval()也会递增计数器,以避免锁定序列。

我无法解决的问题是当您将值手动插入具有UNIQUE或PRIMARY约束以及序列的nextval()作为默认值的字段时会发生什么。据我所知,当序列达到该值时,这会导致INSERT失败。

是否有一种简单(或常见)的​​方法来解决这个问题?

非常感谢明确的解释。

更新:如果您认为我不应该这样做,将永远无法解决这个问题或者做出一些有缺陷的假设,请随时在答案中指出它们。最重要的是,请告诉我该怎么做,为程序员提供一个稳定而强大的数据库,不能通过简单的插入来破坏(最好不要隐藏存储过程背后的所有内容)

5 个答案:

答案 0 :(得分:10)

如果您正在迁移数据,那么我会删除列上的序列约束,执行所有插入操作,使用setval()将序列设置为数据的最大值,然后恢复列序列nextval()默认。

答案 1 :(得分:4)

为了扩展Tometzky的优秀答案,这是一个更通用的版本:

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

答案 2 :(得分:3)

您可以创建一个触发器来检查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');

答案 3 :(得分:2)

我不完全理解你的问题,但是如果你的目标只是做插入,并且有一个有效的字段(例如一个id),那么插入没有id字段的值,这就是“默认”代表的意思。它会起作用。

E.g。如果表定义中id serial NOT NULLCONSTRAINT table_pkey PRIMARY KEY(id)将自动设置ID并自动增加序列table_id_seq

答案 4 :(得分:1)

使用CHECK怎么样?

<p><?php printf( __( 'For Seller resources, click <a href="%s">here</a>.', 'woocommerce' ), 'http://yoururlhere.com' ); ?></p