38.4. Правила для INSERT
, UPDATE
и DELETE
Правила, определяемые для команд INSERT
, UPDATE
и DELETE
, значительно отличаются от правил представлений, описанных в предыдущем разделе. Во-первых, команда CREATE RULE
позволяет создавать правила со следующими особенностями:
Они могут не определять действия.
Они могут определять несколько действий.
Они могут действовать в режиме
INSTEAD
илиALSO
(по умолчанию).Становятся полезными псевдоотношения
NEW
иOLD
.Они могут иметь условия применения.
Во-вторых, они не модифицируют само исходное дерево запроса. Вместо этого они создают несколько новых деревьев запросов и могут заменить исходное.
Внимание
Во многих случаях для задач, выполнимых с использованием правил для INSERT
/UPDATE
/DELETE
, лучше применять триггеры. Оформляются триггеры чуть сложнее, но понять их смысл гораздо проще. К тому же с правилами могут быть получены неожиданные результаты, когда исходный запрос содержит изменчивые функции: в процессе исполнения правил эти функции могут вызываться большее число раз, чем ожидается.
Кроме того, в некоторых случаях эти типы правил вообще нельзя применять; а именно, с предложениями WITH
в исходном запросе и с вложенными подзапросами SELECT
с множественным присваиванием в списке SET
запросов UPDATE
. Это объясняется тем, что копирование этих конструкций в запрос правила привело бы к многократному вычислению вложенного запроса, что пошло бы в разрез с выраженными намерениями автора запроса.
38.4.1. Как работают правила для изменения
Запомните синтаксис:
CREATE [ OR REPLACE ] RULEимя
AS ONсобытие
TOтаблица
[ WHEREусловие
] DO [ ALSO | INSTEAD ] { NOTHING |команда
| (команда
;команда
... ) }
В дальнейшем, под правилами для изменения подразумеваются правила, определяемые для команд INSERT
, UPDATE
или DELETE
.
Правила для изменения применяются системой правил, когда результирующее отношение и тип команды в дереве запроса совпадает с объектом и событием, заданным в команде CREATE RULE
. Для такого правила система правил создаёт список деревьев запросов. Изначально этот список пуст. С правилом может быть связано ноль (ключевое слово NOTHING
), одно или несколько действий. Простоты ради мы рассмотрим правило с одним действием. Правило может иметь, а может не иметь условия применения, и действует в режиме INSTEAD
или ALSO
(по умолчанию).
Что такое условие применения правила? Это условие, которое говорит, когда нужно, а когда не нужно применять действия правила. В этом условии можно обращаться к псевдоотношениям NEW
и/или OLD
, которые представляют целевое отношение (но с особым значением).
Всего есть три варианта формирования деревьев запросов для правила с одним действием.
- Без условия применения в режиме
ALSO
илиINSTEAD
дерево запроса из действия правила с добавленным условием исходного дерева
- С условием применения в режиме
ALSO
дерево запроса из действия правила с условием применения правила и условием, добавленным из исходного дерева
- С условием применения в режиме
INSTEAD
дерево запроса из действия правила с условием применения правила и условием из исходного дерева; также добавляется исходное дерево запроса с условием, обратным условию применения правила
Наконец, для правил ALSO
в список добавляется исходное дерево запроса без изменений. Так как исходное дерево запроса также добавляют только правила INSTEAD
с условиями применения, в итоге для правила с одним действием мы можем получить только одно или два дерева запросов.
Для правил ON INSERT
исходный запрос (если он не перекрывается режимом INSTEAD
) выполняется перед действиями, добавленными правилами. Поэтому эти действия могут видеть вставленные строки. Но для правил ON UPDATE
и ON DELETE
исходный запрос выполняется после действий, добавленных правилами. При таком порядке эти действия будут видеть строки, подлежащие изменению или удалению; иначе бы действия не работали, не найдя строк, соответствующих их условиям применения (эти строки уже будут изменены или удалены).
Деревья запросов, полученные из действий правил, снова попадают в систему перезаписи, где могут примениться дополнительные правила, добавляющие или убирающие деревья запроса. Поэтому действия правила должны выполнять команды другого типа или работать с другим результирующим отношением, иначе возникнет бесконечная рекурсия. (Система выявляет подобное рекурсивное разворачивание правил и выдаёт ошибку.)
Деревья запросов, заданные для действий в системном каталоге pg_rewrite
, представляют собой только шаблоны. Так как они могут обращаться к элементам NEW
и OLD
в списке отношений, их можно будет использовать только после некоторых подстановок. В случае ссылки на NEW
соответствующий элемент ищется в целевом списке исходного запроса. Если он найден, ссылка заменяется выражением этого элемента. В противном случае NEW
означает то же самое, что и OLD
(для команды UPDATE
) или заменяется значением NULL (для команды INSERT
). Любые ссылки на OLD
заменяются ссылкой на элемент результирующего отношения в списке отношений.
После того как система применит все правила для изменения, она применяет правила представления к полученному дереву (или деревьям) запроса. Представления не могут добавлять новые действия для изменения, поэтому нет необходимости применять такие правила к результату перезаписи представления.
38.4.1.1. Пошаговый разбор первого правила
Предположим, что нам нужно отслеживать изменения в столбце sl_avail
таблицы shoelace_data
. Мы можем создать таблицу для ведения журнала и правило, которое будет добавлять в неё записи по условию, когда для shoelace_data
выполняется UPDATE
.
CREATE TABLE shoelace_log ( sl_name text, -- шнурки, количество которых изменилось sl_avail integer, -- новое количество log_who text, -- кто изменил log_when timestamp -- когда ); CREATE RULE log_shoelace AS ON UPDATE TO shoelace_data WHERE NEW.sl_avail <> OLD.sl_avail DO INSERT INTO shoelace_log VALUES ( NEW.sl_name, NEW.sl_avail, current_user, current_timestamp );
Теперь, если кто-то выполнит:
UPDATE shoelace_data SET sl_avail = 6 WHERE sl_name = 'sl7';
мы увидим в таблице журнала:
SELECT * FROM shoelace_log; sl_name | sl_avail | log_who | log_when ---------+----------+---------+---------------------------------- sl7 | 6 | Al | Tue Oct 20 16:14:45 1998 MET DST (1 row)
Именно это нам и нужно. При этом внутри происходит следующее. Анализатор запроса создаёт дерево:
UPDATE shoelace_data SET sl_avail = 6 FROM shoelace_data shoelace_data WHERE shoelace_data.sl_name = 'sl7';
В системном каталоге находится правило log_shoelace
, настроенное на изменение (ON UPDATE
) с условием применения:
NEW.sl_avail <> OLD.sl_avail
и действием:
INSERT INTO shoelace_log VALUES ( new.sl_name, new.sl_avail, current_user, current_timestamp ) FROM shoelace_data new, shoelace_data old;
(Это выглядит несколько странно, так как обычно нельзя написать INSERT ... VALUES ... FROM
. Предложение FROM
здесь добавлено, просто чтобы показать, что в дереве запроса для ссылок new
и old
есть элементы в списке отношений. Они необходимы для того, чтобы к ним могли обращаться переменные в дереве запроса команды INSERT
.)
Так как это правило ALSO
с условием применения, система правил должна выдать два дерева запросов: изменённое действие правила и исходное дерево запроса. На первом шаге список отношений исходного запроса вставляется в дерево действия правила и получается:
INSERT INTO shoelace_log VALUES (
new.sl_name, new.sl_avail,
current_user, current_timestamp )
FROM shoelace_data new, shoelace_data old,
shoelace_data shoelace_data;
На втором шаге в это дерево добавляется условие применения правила, так что результирующий набор ограничивается строками, в которых меняется sl_avail
:
INSERT INTO shoelace_log VALUES (
new.sl_name, new.sl_avail,
current_user, current_timestamp )
FROM shoelace_data new, shoelace_data old,
shoelace_data shoelace_data
WHERE new.sl_avail <> old.sl_avail;
(Это выглядит ещё более странно, ведь в INSERT ... VALUES
не записывается и предложение WHERE
, но планировщик и исполнитель не испытывают затруднений с этим. Они всё равно должны поддерживать эту функциональность для INSERT ... SELECT
.)
На третьем шаге добавляется условие исходного дерева, что ещё больше ограничивает результирующий набор, оставляя в нём только строки, которые затронул бы исходный запрос:
INSERT INTO shoelace_log VALUES (
new.sl_name, new.sl_avail,
current_user, current_timestamp )
FROM shoelace_data new, shoelace_data old,
shoelace_data shoelace_data
WHERE new.sl_avail <> old.sl_avail
AND shoelace_data.sl_name = 'sl7';
На четвёртом шаге ссылки на NEW
заменяются элементами выходного списка из исходного дерева запроса или переменными из результирующего отношения:
INSERT INTO shoelace_log VALUES ( shoelace_data.sl_name, 6, current_user, current_timestamp ) FROM shoelace_data new, shoelace_data old, shoelace_data shoelace_data WHERE 6 <> old.sl_avail AND shoelace_data.sl_name = 'sl7';
На последнем, пятом шаге ссылки на OLD
заменяются ссылками на результирующее отношение:
INSERT INTO shoelace_log VALUES (
shoelace_data.sl_name, 6,
current_user, current_timestamp )
FROM shoelace_data new, shoelace_data old,
shoelace_data shoelace_data
WHERE 6 <> shoelace_data.sl_avail
AND shoelace_data.sl_name = 'sl7';
Вот и всё. Так как правило действует в режиме ALSO
, мы также выводим исходное дерево запроса. Таким образом, система правил выдаёт список с двумя деревьями запросов, соответствующими этим операторам:
INSERT INTO shoelace_log VALUES ( shoelace_data.sl_name, 6, current_user, current_timestamp ) FROM shoelace_data WHERE 6 <> shoelace_data.sl_avail AND shoelace_data.sl_name = 'sl7'; UPDATE shoelace_data SET sl_avail = 6 WHERE sl_name = 'sl7';
Они выполняются в показанном порядке и именно это должно делать данное правило.
Благодаря заменам и добавленным условиям в журнал не добавится запись, например, при таком исходном запросе:
UPDATE shoelace_data SET sl_color = 'green' WHERE sl_name = 'sl7';
В этом случае исходное дерево запроса не содержит элемент выходного списка для sl_avail
, так что NEW.sl_avail
будет заменено переменной shoelace_data.sl_avail
. Таким образом, дополнительная команда, созданная правилом, будет такой:
INSERT INTO shoelace_log VALUES ( shoelace_data.sl_name, shoelace_data.sl_avail, current_user, current_timestamp ) FROM shoelace_data WHERE shoelace_data.sl_avail <> shoelace_data.sl_avail AND shoelace_data.sl_name = 'sl7';
Это условие применения не будет выполняться никогда.
Это также будет работать, если исходный запрос изменяет несколько строк. Так, если кто-то выполнит команду:
UPDATE shoelace_data SET sl_avail = 0 WHERE sl_color = 'black';
фактически будут изменены четыре строки (sl1
, sl2
, sl3
и sl4
). Но для sl3
значение sl_avail = 0
. В этом случае условие исходного дерева другое, так что это правило выдаёт такое дополнительное дерево запроса:
INSERT INTO shoelace_log
SELECT shoelace_data.sl_name, 0,
current_user, current_timestamp
FROM shoelace_data
WHERE 0 <> shoelace_data.sl_avail
AND shoelace_data.sl_color = 'black';
. С таким деревом запроса в журнал определённо будут добавлены три записи. И это абсолютно правильно.
Здесь мы видим, почему важно, чтобы исходное дерево запроса выполнялось в конце. Если бы оператор UPDATE
выполнился сначала, все строки уже получили бы нулевые значения, так что записывающий в журнал INSERT
не нашёл бы строк, в которых 0 <> shoelace_data.sl_avail
.
38.4.2. Сочетание с представлениями
Есть один простой вариант защититься от ранее упомянутой возможности выполнять INSERT
, UPDATE
или DELETE
для представлений, когда это нежелательно — создать правила, просто отбрасывающие деревья этих запросов. В нашем случае они будут выглядеть так:
CREATE RULE shoe_ins_protect AS ON INSERT TO shoe DO INSTEAD NOTHING; CREATE RULE shoe_upd_protect AS ON UPDATE TO shoe DO INSTEAD NOTHING; CREATE RULE shoe_del_protect AS ON DELETE TO shoe DO INSTEAD NOTHING;
Если теперь кто-то попытается выполнить одну из этих операций с представлением shoe
, система правил применит эти правила. Так как это правила без действий в режиме INSTEAD
, результирующий список деревьев запроса будет пуст и весь запрос аннулируется, так что после работы системы правил будет нечего оптимизировать и выполнять.
Более сложный вариант — использовать систему правил для создания правил, преобразующих дерево запроса в выполняющее нужную операцию с реальными таблицами. Чтобы реализовать это с представлением shoelace
, мы создадим следующие правила:
CREATE RULE shoelace_ins AS ON INSERT TO shoelace DO INSTEAD INSERT INTO shoelace_data VALUES ( NEW.sl_name, NEW.sl_avail, NEW.sl_color, NEW.sl_len, NEW.sl_unit ); CREATE RULE shoelace_upd AS ON UPDATE TO shoelace DO INSTEAD UPDATE shoelace_data SET sl_name = NEW.sl_name, sl_avail = NEW.sl_avail, sl_color = NEW.sl_color, sl_len = NEW.sl_len, sl_unit = NEW.sl_unit WHERE sl_name = OLD.sl_name; CREATE RULE shoelace_del AS ON DELETE TO shoelace DO INSTEAD DELETE FROM shoelace_data WHERE sl_name = OLD.sl_name;
Если вы хотите поддерживать также запросы к представлению с RETURNING
, вам надо создать правила с предложениями RETURNING
, которые будут вычислять строки представления. Это обычно довольно тривиально для представлений с одной нижележащей таблицей, но несколько затруднительно для представлений с соединением, таких как shoelace
. Например, для INSERT
это будет выглядеть так:
CREATE RULE shoelace_ins AS ON INSERT TO shoelace DO INSTEAD INSERT INTO shoelace_data VALUES ( NEW.sl_name, NEW.sl_avail, NEW.sl_color, NEW.sl_len, NEW.sl_unit ) RETURNING shoelace_data.*, (SELECT shoelace_data.sl_len * u.un_fact FROM unit u WHERE shoelace_data.sl_unit = u.un_name);
Заметьте, что это одно правило поддерживает запросы и INSERT
, и INSERT RETURNING
к этому представлению — предложение RETURNING
просто игнорируется при обычном INSERT
.
Теперь предположим, что на фабрику прибывает партия шнурков с объёмной сопроводительной накладной. Но вы не хотите вручную вносить по одной записи в представление shoelace
. Вместо этого можно создать две маленькие таблицы: в первую вы будете вставлять записи из накладной, а вторая пригодится для специального приёма. Для этого мы выполним следующие команды:
CREATE TABLE shoelace_arrive ( arr_name text, arr_quant integer ); CREATE TABLE shoelace_ok ( ok_name text, ok_quant integer ); CREATE RULE shoelace_ok_ins AS ON INSERT TO shoelace_ok DO INSTEAD UPDATE shoelace SET sl_avail = sl_avail + NEW.ok_quant WHERE sl_name = NEW.ok_name;
Теперь вы можете наполнить таблицу shoelace_arrive
данными о поступивших шнурках из накладной:
SELECT * FROM shoelace_arrive; arr_name | arr_quant ----------+----------- sl3 | 10 sl6 | 20 sl8 | 20 (3 rows)
Взгляните на текущие данные:
SELECT * FROM shoelace; sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm ----------+----------+----------+--------+---------+----------- sl1 | 5 | black | 80 | cm | 80 sl2 | 6 | black | 100 | cm | 100 sl7 | 6 | brown | 60 | cm | 60 sl3 | 0 | black | 35 | inch | 88.9 sl4 | 8 | black | 40 | inch | 101.6 sl8 | 1 | brown | 40 | inch | 101.6 sl5 | 4 | brown | 1 | m | 100 sl6 | 0 | brown | 0.9 | m | 90 (8 rows)
Теперь переместите прибывшие шнурки во вторую таблицу:
INSERT INTO shoelace_ok SELECT * FROM shoelace_arrive;
Проверьте, что получилось:
SELECT * FROM shoelace ORDER BY sl_name; sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm ----------+----------+----------+--------+---------+----------- sl1 | 5 | black | 80 | cm | 80 sl2 | 6 | black | 100 | cm | 100 sl7 | 6 | brown | 60 | cm | 60 sl4 | 8 | black | 40 | inch | 101.6 sl3 | 10 | black | 35 | inch | 88.9 sl8 | 21 | brown | 40 | inch | 101.6 sl5 | 4 | brown | 1 | m | 100 sl6 | 20 | brown | 0.9 | m | 90 (8 rows) SELECT * FROM shoelace_log; sl_name | sl_avail | log_who| log_when ---------+----------+--------+---------------------------------- sl7 | 6 | Al | Tue Oct 20 19:14:45 1998 MET DST sl3 | 10 | Al | Tue Oct 20 19:25:16 1998 MET DST sl6 | 20 | Al | Tue Oct 20 19:25:16 1998 MET DST sl8 | 21 | Al | Tue Oct 20 19:25:16 1998 MET DST (4 rows)
Чтобы получить эти результаты из одного INSERT ... SELECT
, была проделана большая работа. Мы подробно опишем всё преобразование дерева запросов в продолжении этой главы. Начнём с дерева, выданного анализатором запроса:
INSERT INTO shoelace_ok SELECT shoelace_arrive.arr_name, shoelace_arrive.arr_quant FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok;
Теперь применяется первое правило shoelace_ok_ins
, создающее такое дерево:
UPDATE shoelace SET sl_avail = shoelace.sl_avail + shoelace_arrive.arr_quant FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok, shoelace_ok old, shoelace_ok new, shoelace shoelace WHERE shoelace.sl_name = shoelace_arrive.arr_name;
и отбрасывающее исходный INSERT
в shoelace_ok
. Этот переписанный запрос снова поступает в систему правил и второе применяемое правило shoelace_upd
выдаёт:
UPDATE shoelace_data SET sl_name = shoelace.sl_name, sl_avail = shoelace.sl_avail + shoelace_arrive.arr_quant, sl_color = shoelace.sl_color, sl_len = shoelace.sl_len, sl_unit = shoelace.sl_unit FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok, shoelace_ok old, shoelace_ok new, shoelace shoelace, shoelace old, shoelace new, shoelace_data shoelace_data WHERE shoelace.sl_name = shoelace_arrive.arr_name AND shoelace_data.sl_name = shoelace.sl_name;
Это тоже правило INSTEAD
, так что предыдущее дерево запроса отбрасывается. Заметьте, что этот запрос по-прежнему использует представление shoelace
. Но система правил ещё не закончила свою работу, она продолжает и применяет правило _RETURN
, так что мы получаем:
UPDATE shoelace_data SET sl_name = s.sl_name, sl_avail = s.sl_avail + shoelace_arrive.arr_quant, sl_color = s.sl_color, sl_len = s.sl_len, sl_unit = s.sl_unit FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok, shoelace_ok old, shoelace_ok new, shoelace shoelace, shoelace old, shoelace new, shoelace_data shoelace_data, shoelace old, shoelace new, shoelace_data s, unit u WHERE s.sl_name = shoelace_arrive.arr_name AND shoelace_data.sl_name = s.sl_name;
Наконец, применяется правило log_shoelace
и выдаётся дополнительное дерево запроса:
INSERT INTO shoelace_log SELECT s.sl_name, s.sl_avail + shoelace_arrive.arr_quant, current_user, current_timestamp FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok, shoelace_ok old, shoelace_ok new, shoelace shoelace, shoelace old, shoelace new, shoelace_data shoelace_data, shoelace old, shoelace new, shoelace_data s, unit u, shoelace_data old, shoelace_data new shoelace_log shoelace_log WHERE s.sl_name = shoelace_arrive.arr_name AND shoelace_data.sl_name = s.sl_name AND (s.sl_avail + shoelace_arrive.arr_quant) <> s.sl_avail;
Теперь, обработав все правила, система правил выдаёт построенные деревья запросов.
В итоге мы получаем два дерева запросов, равнозначные следующим операторам SQL:
INSERT INTO shoelace_log SELECT s.sl_name, s.sl_avail + shoelace_arrive.arr_quant, current_user, current_timestamp FROM shoelace_arrive shoelace_arrive, shoelace_data shoelace_data, shoelace_data s WHERE s.sl_name = shoelace_arrive.arr_name AND shoelace_data.sl_name = s.sl_name AND s.sl_avail + shoelace_arrive.arr_quant <> s.sl_avail; UPDATE shoelace_data SET sl_avail = shoelace_data.sl_avail + shoelace_arrive.arr_quant FROM shoelace_arrive shoelace_arrive, shoelace_data shoelace_data, shoelace_data s WHERE s.sl_name = shoelace_arrive.sl_name AND shoelace_data.sl_name = s.sl_name;
В результате вся операция, в ходе которой данные, поступающие из одного отношения, вставляются в другое, вставка преобразуется в изменение третьего, что затем становится изменением четвёртого, и запись об этом изменении добавляется в пятое, сводится к двум запросам.
Здесь можно заметить маленькую не очень красивую деталь. Как видно, в этих двух запросах таблица shoelace_data
фигурирует в списке отношений дважды, тогда как определённо достаточно и одного вхождения. Планировщик не понимает этого и поэтому для дерева запроса INSERT
, выданного системой правил, будет получен такой план:
Nested Loop -> Merge Join -> Seq Scan -> Sort -> Seq Scan on s -> Seq Scan -> Sort -> Seq Scan on shoelace_arrive -> Seq Scan on shoelace_data
Тогда как без лишнего элемента в списке отношений мы получили бы:
Merge Join -> Seq Scan -> Sort -> Seq Scan on s -> Seq Scan -> Sort -> Seq Scan on shoelace_arrive
При этом в журнале оказались бы точно такие же записи. Таким образом, применение правил повлекло дополнительное сканирование таблицы shoelace_data
, в котором не было никакой необходимости. И такое же избыточное сканирование выполняется ещё раз в UPDATE
. Отнеситесь к этому с пониманием, ведь сделать всё это возможным в принципе было действительно сложно.
И наконец, ещё одна, завершающая демонстрация системы правил PostgreSQL и всей её мощи. Предположим, что вы добавили в базу данных шнурки с экстраординарными цветами:
INSERT INTO shoelace VALUES ('sl9', 0, 'pink', 35.0, 'inch', 0.0); INSERT INTO shoelace VALUES ('sl10', 1000, 'magenta', 40.0, 'inch', 0.0);
Давайте создадим представление, чтобы убедиться, что шнурки (записи в shoelace
) не подходят ни к каким туфлям. Оно будет определено так:
CREATE VIEW shoelace_mismatch AS SELECT * FROM shoelace WHERE NOT EXISTS (SELECT shoename FROM shoe WHERE slcolor = sl_color);
Через него мы получаем наши записи:
SELECT * FROM shoelace_mismatch; sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm ---------+----------+----------+--------+---------+----------- sl9 | 0 | pink | 35 | inch | 88.9 sl10 | 1000 | magenta | 40 | inch | 101.6
Теперь мы хотим, чтобы шнурки, которые ни к чему не подходят, удалялись из базы данных. Чтобы немного усложнить задачу для PostgreSQL, мы не будем удалять их непосредственно из таблицы. Вместо этого мы создадим ещё одно представление:
CREATE VIEW shoelace_can_delete AS SELECT * FROM shoelace_mismatch WHERE sl_avail = 0;
И удалим их так:
DELETE FROM shoelace WHERE EXISTS (SELECT * FROM shoelace_can_delete WHERE sl_name = shoelace.sl_name);
Вуаля:
SELECT * FROM shoelace; sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm ---------+----------+----------+--------+---------+----------- sl1 | 5 | black | 80 | cm | 80 sl2 | 6 | black | 100 | cm | 100 sl7 | 6 | brown | 60 | cm | 60 sl4 | 8 | black | 40 | inch | 101.6 sl3 | 10 | black | 35 | inch | 88.9 sl8 | 21 | brown | 40 | inch | 101.6 sl10 | 1000 | magenta | 40 | inch | 101.6 sl5 | 4 | brown | 1 | m | 100 sl6 | 20 | brown | 0.9 | m | 90 (9 rows)
Так запрос DELETE
для представления с ограничивающим условием-подзапросом, использующим в совокупности 4 вложенных/соединённых представления, с одним из которых тоже связано условие с подзапросом, задействующим представление, и где используются вычисляемые столбцы представлений, переписывается и преобразуется в одно дерево запроса, которое удаляет требуемые данные из реальной таблицы.
На практике ситуации, когда необходима такая сложная конструкция, встречаются довольно редко, но, тем не менее, приятно осознавать, что всё это возможно и работает.