Ниже приведен код, который я использую в базе данных PostgreSQL 16. Каждый раз, когда я пытаюсь обновить зарплату, я получаю:
ОШИБКА: в правилах для отношения «сотрудники» обнаружена бесконечная рекурсия
Состояние SQL: 42P17"
Используемый объект
create table rowan.employees (emp_id serial primary key, name varchar(100), salary numeric);
Вставка значений
insert into rowan.employees (name, salary) values
('John Doe', 50000),
('Jane Doe', 60000),
('Rafael Orta',80000);
Создание правила
create or replace rule update_salary
as on update to rowan.employees
where new.salary > 70000
and pg_trigger_depth() = 0
do instead
update rowan.employees
set salary = 70000
where emp_id = new.emp_id;
Выполнение обновления
update rowan.employees set salary = 80000 where emp_id = 3;
Проверка значений
select * from rowan.employees;
Я пытаюсь выполнить команду ниже:
update rowan.employees set salary = 80000 where emp_id = 3;
Я ожидал, что он обновит зарплату до 70000.
Это было предложено ИИ как решение моей проблемы, но без этого оно не работает. «pg_trigger_length() — это функция, которая возвращает текущую глубину выполнения триггера. Проверяя, равно ли оно 0, вы гарантируете, что правило применяется только во время начальной операции обновления, а не во время любых последующих обновлений, инициируемых самим правилом».
Да, это ничего не делает, правила не являются триггерами.
Так вы сворачиваете новую зарплату в обновлениях. Разве вы не хотели бы того же для вставок? Кроме того, вы упоминаете «представление» в заголовке, но в тексте вопроса его нет?
Вне триггера pg_trigger_length() всегда равен 0.
Возможно, вы захотите использовать для этого триггер, как показано ниже (непроверено):
CREATE FUNCTION cap_salary() RETURNS trigger
AS $$
BEGIN
IF NEW.salary > 70000
THEN
NEW.salary = 70000;
FI;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER cap_salary_on_update
BEFORE UPDATE ON rowan.employees
FOR EACH ROW
EXECUTE FUNCTION cap_salary();
Вызов системы правил является проблемой, поскольку она сначала переписывает запрос, прежде чем что-либо будет выполнено. Условие WHERE
будет частью переписанного запроса, но оно по-прежнему включает UPDATE
on rowan.employees
, т. е. правило выполняется снова — и снова и снова. Обратите внимание, что WHERE
будет включен в переписанный запрос и не будет проверен перед переписыванием запроса (это включает в себя то, что все запросы UPDATE
будут переписаны, независимо от того, выполнено ли условие).
Я с вами согласен, но это для класса, и мне нужно продемонстрировать пример использования правил в PostgreSQL. Кроме того, мне трудно понять, что не так с моим кодом; Мне любопытно, почему он выдает мне эту ошибку.
Затем выберите другой пример, например, из документации (postgresql.org/docs/current/rules-update.html). Повторяю еще раз: правило перезаписывает запрос независимо от того, выполняется условие или нет. Результирующий запрос снова представляет собой UPDATE
на rowan.employees
, поэтому правило вызывается снова. Это приводит к бесконечному циклу/рекурсии, и именно поэтому PostgreSQL выдает ошибку.
@RafaelOrta: Если ваш учитель требует от вас написать обычай RULE
, он ошибается. Правила в основном существуют для реализации представлений. Не прикасайтесь к правилам, если вы точно не знаете, что делаете. Вряд ли попытка простого смертного даст именно то, что должна.
@RafaelOrta Посмотрите почему бы и нет и две связанные статьи по этой теме.
В PostgreSQL нет хорошего варианта использования правил, кроме реализации представлений. Ваше правило циклично, pg_trigger_depth()
бесполезно и, возможно, должно полагаться на человеческий интеллект.
Слушайте комментарии и используйте триггеры вместо правил.
Если вы хотите использовать правило, то простой пример:
create table rule_test (id integer, fld_1 integer);
insert into rule_test values (1, 5), (2, 7);
create view rule_test_vw as select * from rule_test ;
create or replace rule update_do_nothing as on update to rule_test_vw do instead nothing;
create or replace rule update_do_something as on update to rule_test_vw where new.fld_1 > 10 do instead update rule_test set fld_1 = 10 where id = new.id;
create or replace rule update_do_passthru as on update to rule_test_vw where new.fld_1 <= 10 do instead update rule_test set fld_1 = new.fld_1 where id = new.id;
update rule_test_vw set fld_1 = 15 where id = 1;
update rule_test_vw set fld_1 = 3 where id = 2;
select * from rule_test;
id | fld_1
----+-------
1 | 10
2 | 3
По сути, создайте представление над таблицей и используйте do instead
против представления, а instead
действует против таблицы. Это решит проблему рекурсии.
PostgreSQL дешевле проверить условие перед вызовом триггерной функции:
CREATE FUNCTION cap_salary() RETURNS trigger AS $f$
BEGIN --trigger won't fire unless the salary needs to be reduced
NEW.salary = 70000;--so no need for an `IF`
RETURN NEW;
END $f$ LANGUAGE plpgsql;
CREATE TRIGGER cap_salary_on_update
BEFORE INSERT OR UPDATE OF salary ON rowan.employees
FOR EACH ROW
WHEN (NEW.salary > 70000) --this limits when the trigger fires
EXECUTE FUNCTION cap_salary();
Он достигает той же цели, но вы экономите некоторое время выполнения, избегая более дорогостоящих вызовов PL/pgSQL, когда в них нет необходимости.
Вы можете сравнить выполнение триггера time=1114.413 calls=200003
в этой демо с time=334.140 calls=60373
в этой демо.
Вам нужен только один: демо
begin;
alter table rowan.employees rename to t_employees;
create view rowan.employees as table rowan.t_employees;
create rule salary_limit as
on update to rowan.employees
do instead update rowan.t_employees
set salary = case when new.salary <= 70000
or new.salary is null
then new.salary
else 70000 end
where emp_id is not distinct from new.emp_id
returning *;
commit;
view
, но система правил также может перенаправлять запросы с обычного table
. Правило должно перехватывать все запросы, направленные на объект, выдающий себя за таблицу, и перенаправлять их на реальную таблицу.case
может быть предпочтительнее, чем использование нескольких условных правил. Наличие условия правила не позволяет вам добавить предложение returning
, что, в свою очередь, нарушает все вызовы, которые хотели бы его использовать. Некоторые клиентские приложения, библиотеки и ORM добавляют предложение , возвращающее , по умолчанию, независимо от того, используете вы его или нет — нарушение, которое может привести к трудно отслеживаемым ошибкам.null
как для поля salary
, так и для поля emp_id
. Его необходимо добавить к case
, а условие where
следует переключить с нулевого небезопасного =
на is not distinct from
- в противном случае правило не будет обрабатывать null
зарплаты или обновления, нацеленные на все строки, особенно на те, у которых есть null
в emp_id
.emp_id
- это primary key
, что подразумевает, что это not null
- это строго необходимо, когда нет явного или подразумеваемого not null
ограничения.
Я не думаю, что pg_trigger_length() будет работать в правиле. Вне триггеров оно просто возвращает 0. Кроме того, правило может просто обнаруживать, что вы заменяете обновление обновлением, что может вызывать повышение флагов бесконечной рекурсии без подробной проверки условий.