39.4. Правила для INSERT, UPDATE и DELETE#

39.4. Правила для INSERT, UPDATE и DELETE

39.4. Правила для INSERT, UPDATE и DELETE

Правила, которые определены для команды INSERT, UPDATE и DELETE, существенно отличаются от правил представлений, описанных в предыдущем разделе. Во-первых, их команда CREATE RULE позволяет больше:

  • Они могут не иметь действия.

  • Они могут иметь несколько действий.

  • Они могут быть INSTEAD или ALSO (по умолчанию).

  • Псевдоотношения NEW и OLD становятся полезными.

  • Они могут иметь квалификации правил.

Во-первых, они не изменяют дерево запроса на месте. Вместо этого они создают ноль или более новых деревьев запросов и могут отбросить исходное.

Предостережение

Во многих случаях задачи, которые могут быть выполнены с помощью правил на INSERT/UPDATE/DELETE, лучше выполнять с помощью триггеров. Триггеры немного сложнее в нотации, но их семантика гораздо проще для понимания. Правила имеют тенденцию давать неожиданные результаты, когда исходный запрос содержит изменчивые функции: изменчивые функции могут выполняться больше раз, чем ожидалось в процессе выполнения правил.

Также есть некоторые случаи, которые не поддерживаются этими типами правил вообще, в частности, включая предложение WITH в исходном запросе и подзапросы с множественным присваиванием в списке SET запросов UPDATE. Это связано с тем, что копирование этих конструкций в запрос правила приведет к множественным вычислениям подзапроса, что противоречит явному намерению автора запроса.

39.4.1. Как работают правила обновления

Сохранить синтаксис:

CREATE [ OR REPLACE ] RULE name AS ON event
    TO table [ WHERE condition ]
    DO [ ALSO | INSTEAD ] { NOTHING | command | ( command ; command ... ) }

Имейте в виду. В следующем, правила обновления означает правила, которые определены на INSERT, UPDATE или DELETE.

Все правила обновления применяются системой правил, когда результат отношения и тип команды дерева запроса равны объекту и событию, указанным в команде CREATE RULE. Для правил обновления система правил создает список деревьев запросов. Изначально список деревьев запросов пуст. Может быть ноль (NOTHING ключевое слово), одно или несколько действий. Для упрощения рассмотрим правило с одним действием. Это правило может иметь квалификацию или не иметь и может быть INSTEAD или ALSO (по умолчанию).

Что такое квалификация правила? Это ограничение, которое указывает, когда действия правила должны быть выполнены, а когда нет. Эта квалификация может ссылаться только на псевдоотношения NEW и/или OLD, которые в основном представляют отношение, которое было дано в качестве объекта (но с особым значением).

Итак, у нас есть три случая, которые порождают следующие деревья запросов для правила с одним действием.

No qualification, with either ALSO or INSTEAD

дерево запроса из действия правила с добавленной квалификацией исходного дерева запроса

Qualification given and ALSO

дерево запроса из действия правила с квалификацией правила и квалификацией исходного дерева запроса, добавленной

Qualification given and INSTEAD

дерево запроса из действия правила с квалификацией правила и квалификацией исходного дерева запроса; и исходное дерево запроса с добавленной отрицательной квалификацией правила

Наконец, если правило - это ALSO, неизменное исходное дерево запроса добавляется в список. Поскольку только квалифицированные правила INSTEAD уже добавляют исходное дерево запроса, мы получаем одно или два дерева запроса на выходе для правила с одним действием.

Для правил ON INSERT оригинальный запрос (если не подавлен с помощью INSTEAD) выполняется перед любыми действиями, добавленными правилами. Это позволяет действиям видеть вставленные строки. Но для правил ON UPDATE и ON DELETE оригинальный запрос выполняется после действий, добавленных правилами. Это гарантирует, что действия могут видеть строки, которые будут обновлены или удалены; в противном случае действия могут ничего не делать, потому что они не найдут строки, соответствующие их условиям.

Все деревья запросов, сгенерированные из действий правил, снова передаются в систему переписывания, и, возможно, применяются дополнительные или меньшее количество правил, что приводит к появлению дополнительных или меньшего количества деревьев запросов. Поэтому действия правила должны иметь либо другой тип команды, либо другое отношение результата, чем само правило, иначе этот рекурсивный процесс приведет к бесконечному циклу. (Рекурсивное расширение правила будет обнаружено и сообщено как ошибка).

Все деревья запросов, найденные в действиях системного каталога pg_rewrite, являются только шаблонами. Поскольку они могут ссылаться на записи диапазона для NEW и OLD, перед их использованием необходимо внести некоторые замены. Для любой ссылки на NEW производится поиск соответствующей записи в целевом списке исходного запроса. Если запись найдена, то выражение этой записи заменяет ссылку. В противном случае, NEW означает то же самое, что и OLD (для UPDATE) или заменяется на значение null (для INSERT). Любая ссылка на OLD заменяется ссылкой на запись диапазона, которая является результатом отношения.

После того, как система завершит применение правил обновления, она применяет правила представлений к полученному дереву(деревьям) запроса. Представления не могут вставлять новые действия обновления, поэтому нет необходимости применять правила обновления к результату переписывания представления.

39.4.1.1. Первое правило пошагово

Скажем, мы хотим отслеживать изменения в столбце sl_avail в отношении shoelace_data. Для этого мы создаем таблицу журнала и правило, которое условно записывает запись в журнал при выполнении команды UPDATE на shoelace_data.

CREATE TABLE shoelace_log (
    sl_name    text,          -- shoelace changed
    sl_avail   integer,       -- new available value
    log_who    text,          -- who did it
    log_when   timestamp      -- when
);

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, поэтому система правил должна вернуть два дерева запросов: измененное действие правила и исходное дерево запроса. На шаге 1 таблица диапазона исходного запроса включается в дерево запроса действия правила. Это приводит к следующему результату:

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;

В шаге 2 к нему добавляется условие правила, поэтому результат ограничивается только строками, где изменяется 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).

В шаге 3 квалификация исходного дерева запроса добавляется, ограничивая результаты дальнейшими строками, которые были бы затронуты исходным запросом:

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';

Шаг 4 заменяет ссылки на 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';

Шаг 5 изменяет ссылки 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';

и это условие никогда не будет true.

Это также будет работать, если исходный запрос изменяет несколько строк. Так что, если кто-то выполнил команду:

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';

being generated by the rule. This query tree will surely insert three new log entries. And that's absolutely correct.

Здесь мы можем увидеть, почему важно, чтобы исходное дерево запроса выполнялось последним. Если бы сначала была выполнена команда UPDATE, все строки уже были бы установлены в ноль, поэтому команда INSERT для журналирования не нашла бы ни одной строки, где 0 <> shoelace_data.sl_avail.

39.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. Пример для случая вставки:

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. Но это была действительно трудная задача, чтобы сделать все это возможным.

Теперь мы проведем окончательную демонстрацию системы правил Tantor SE и ее мощности. Предположим, вы добавляете в свою базу данных некоторые шнурки с необычными цветами:

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

Теперь мы хотим настроить так, чтобы несоответствующие шнурки, которых нет в наличии, удалялись из базы данных. Чтобы сделать это немного сложнее для Tantor SE, мы не удаляем их напрямую. Вместо этого мы создаем еще одну представление:

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)

A DELETE on a view, with a subquery qualification that in total uses 4 nesting/joined views, where one of them itself has a subquery qualification containing a view and where calculated view columns are used, gets rewritten into one single query tree that deletes the requested data from a real table.

В реальном мире, вероятно, есть только несколько ситуаций, где такая конструкция необходима. Но это заставляет вас чувствовать себя уверенно в том, что она работает.