Я хочу иметь порядковый столбец, в котором значения всегда начинаются с 1 и не имеют пробелов.Я разработал решение с помощью триггеров, но я хотел бы знать, есть ли лучший или более элегантный способ.
ПЕРЕД триггером INSERT перенумеровывает строки, которые идут после вставленного значения.Если значение не указано или слишком велико, для него устанавливается число строк + 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();