序号栏没有间隙

时间:2018-10-27 23:58:04

标签: postgresql

我想要一个序数列,其中值始终从1开始且没有间隔。我已经设计了使用触发器的解决方案,但是我想知道是否有更好或更优雅的方法。

在插入前触发器会重新编号插入值之后的行。如果未提供值或该值太大,则将其设置为行数+1。类似地,AFTER DELETE触发器将对删除的值之后的行进行重新编号。这两个触发器都会在更改值之前锁定行。

CREATE OR REPLACE FUNCTION ids_insert() RETURNS trigger AS $BODY$
DECLARE
    _lock_sql text;
    _id bigint;
BEGIN
    IF TG_OP = 'INSERT' THEN
        IF NEW.id < 1 THEN
            RAISE EXCEPTION 'ID must be greater than zero.';
        END IF;
        EXECUTE format('SELECT COUNT(*) + 1 FROM %I', TG_TABLE_NAME)
            INTO _id;
        IF NEW.id IS NULL OR NEW.id > _id THEN
            NEW.id := _id;
        ELSE
            _lock_sql := format(
                'SELECT id FROM %I '
                'WHERE id >= %s '
                'ORDER BY id DESC '
                'FOR UPDATE', TG_TABLE_NAME, NEW.id
            );
            FOR _id IN EXECUTE _lock_sql LOOP
                EXECUTE format('UPDATE %I SET id = id + 1 WHERE id = %s', TG_TABLE_NAME, _id);
            END LOOP;
        END IF;
    ELSE
        IF NEW.id != OLD.id THEN
            RAISE EXCEPTION 'Changing the ID directly is not allowed.';
        END IF;
    END IF;

    RETURN NEW;
END;
$BODY$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION ids_delete() RETURNS trigger AS $BODY$
DECLARE
    _lock_sql text;
    _id bigint;
BEGIN
    _lock_sql := format(
        'SELECT id FROM %I '
        'WHERE id > %s '
        'ORDER BY id '
        'FOR UPDATE', TG_TABLE_NAME, OLD.id
    );
    FOR _id IN EXECUTE _lock_sql LOOP
        EXECUTE format('UPDATE %I SET id = id - 1 WHERE id = %s', TG_TABLE_NAME, _id);
    END LOOP;
    RETURN OLD;
END;
$BODY$ LANGUAGE plpgsql;

CREATE TABLE test (
    id bigint PRIMARY KEY,
    ...
)

CREATE TRIGGER test_insert BEFORE INSERT OR UPDATE OF id ON test
    FOR EACH ROW WHEN (pg_trigger_depth() < 1) EXECUTE PROCEDURE ids_insert();

CREATE TRIGGER test_delete AFTER DELETE ON test
    FOR EACH ROW EXECUTE PROCEDURE ids_delete();

0 个答案:

没有答案