У меня есть база данных PostgreSQL со следующими таблицами
person
city
country
Каждый человек связан с городом через внешний ключ city_id
, и каждый город связан со страной через country_id
аналогичным образом.
Чтобы легко просматривать названия городов и стран каждого человека, я создаю представление:
CREATE VIEW person_view AS
SELECT
person.id,
person.name,
city.name as city,
country.name as country
FROM person
LEFT JOIN city ON person.city_id = city.id
LEFT JOIN country ON city.country_id = country.id
Что дает что-то легкое для чтения.
| id | name | city | country |
------------------------------------------
| 1 | Steve | New York | United States |
| 2 | Rachel | Paris | France |
Теперь, используя такую программу, как dbeaver, я надеялся управлять этими записями с помощью этого представления. Вместо того, чтобы искать идентификаторы всякий раз, когда необходимо изменить город/страну человека, было бы намного проще просто ввести изменения в представлении и перенести эти изменения в исходные таблицы.
Я думал, что именно для этого предназначено обновляемое представление, но dbeaver не позволяет напрямую обновлять это представление и предлагает реализовать INSTEAD OF UPDATE
триггеры или ON UPDATE DO INSTEAD
правила.
Правильно ли я подхожу к этому? Является ли описанная здесь операция тем, для чего предназначены обновляемые представления?
Предложение DBeaver правильное. Представление, использующее соединения, можно сделать обновляемым только в том случае, если вы реализуете триггер INSTEAD OF.
@Belayer спасибо, у вас есть предложения, как к этому можно подойти?
Вы реализуете триггер Instead of
. Триггер определяет, какие столбцы и какие таблицы должны быть обновлены, и делает это соответствующим образом. Это может включать обновление одного столбца или нескольких столбцов в нескольких таблицах. (По предоставленным данным похоже, что только persons
таблица нуждается в обновлении, но возможно, что все 3 нуждаются в обновлении. Только вы можете решить это.)
демо
Показывать только триггер обновления. Вы можете сделать то же самое для вставки и удаления.
CREATE OR REPLACE FUNCTION person_view_upd_trig_fn ()
RETURNS TRIGGER
AS $$
BEGIN
IF tg_op = 'UPDATE' THEN
IF NEW.name <> OLD.name THEN
RAISE NOTICE 'update person name';
UPDATE
person
SET
name = NEW.name
WHERE
id = OLD.id;
RETURN new;
END IF;
IF NEW.city <> OLD.city AND NEW.country = OLD.country THEN
RAISE NOTICE 'update city name';
IF (
SELECT
count(DISTINCT country_id)
FROM
city
WHERE
name = OLD.city OR name = NEW.city) = 2 THEN
RAISE EXCEPTION 'not good';
END IF;
IF (
SELECT
city_id
FROM
city
WHERE
name = NEW.city) IS NULL THEN
RAISE EXCEPTION 'city not in the list';
END IF;
UPDATE
person
SET
city_id = (
SELECT
city_id
FROM
city
WHERE
name = NEW.city)
WHERE
id = OLD.id;
RETURN new;
END IF;
IF NEW.country <> OLD.country AND NEW.city <> OLD.city THEN
RAISE NOTICE 'updating person country & city';
IF NOT EXISTS (
SELECT
FROM
country
WHERE
name = NEW.country) THEN
RAISE EXCEPTION 'not good';
END IF;
UPDATE
person
SET
city_id = (
SELECT
city_id
FROM
city
WHERE
name = NEW.city)
WHERE
id = OLD.id;
RETURN new;
END IF;
RAISE NOTICE 'new.person_view:%', new;
RAISE NOTICE 'old.person_view:%', old;
RETURN NULL;
END IF;
END
$$
LANGUAGE plpgsql;
создать триггер:
CREATE TRIGGER person_view_upd_trig
INSTEAD OF UPDATE ON person_view
FOR EACH ROW EXECUTE PROCEDURE person_view_upd_trig_fn();
Столбец идентификатора person_view не может обновляться, обновление не будет иметь никакого эффекта. все остальные 3 столбца могут обновляться.
IF tg_op = 'UPDATE' THEN
не так уж и избыточен. Вы можете добавить дополнительный блок управления, например IF tg_op = 'DELETE THEN
в эту функцию. Затем одна функция, вы можете управлять 3 действиями/триггерами (удалить, обновить, вставить) вместо 3 функций и 3 триггеров.
https://www.postgresql.org/docs/current/plpgsql-trigger.html
Триггерная функция должна возвращать либо NULL, либо значение записи/строки. имея именно ту структуру таблицы, для которой сработал триггер.
и
INSTEAD OF триггеры (которые всегда являются триггерами уровня строки и могут использоваться в представлениях) могут возвращать значение null, чтобы сигнализировать о том, что они не выполнялись. любые обновления, и что остальная часть операции для этой строки должна быть пропущены (т. е. последующие триггеры не срабатывают, и строка не учитываются в статусе затронутых строк для окружающих ВСТАВИТЬ/ОБНОВИТЬ/УДАЛИТЬ). В противном случае должно быть возвращено ненулевое значение, чтобы сигнализировать о том, что триггер выполнил запрошенную операцию. Для INSERT и UPDATE, возвращаемое значение должно быть NEW, что функция триггера может измениться для поддержки INSERT RETURNING и UPDATE RETURNING (это также повлияет на значение строки, передаваемое в любой последующие триггеры или переданы в специальную ссылку псевдонима EXCLUDED в операторе INSERT с предложением ON CONFLICT DO UPDATE). Для DELETE, возвращаемое значение должно быть OLD.
Блестящий. Это именно то, что я искал. Спасибо за демонстрацию скрипки.
Можете ли вы дать мне какое-либо представление о том, что эти функции должны возвращать? Я вижу, что он выполняет UPDATE
в таблице person
, что является ожидаемым поведением. В чем разница между возвратом new
и NULL
? Что он делает с new
?
Второй вопрос: является ли избыточным tg_op = 'UPDATE'
, учитывая, что при создании триггера мы указываем INSTEAD OF UPDATE ON person_view
@AndrewPlowright Я обновляю вопрос. Надеюсь, это ответит на ваш вопрос.
Именно Postgres, а не dbeaver, препятствует обновлению этого представления. Это не обновляемый вид. Обновляемые представления не могут иметь присоединение и другие ограничения. (см. Обновляемые виды). Так что вы не подходите к этому правильно.