39.4. Regras para INSERT, UPDATE e DELETE #

39.4.1. Como funcionam as regras de atualização
39.4.2. Cooperação com visões

As regras definidas para os comandos INSERT, UPDATE, e DELETE, são muito diferentes das regras de visão descritas nas seções anteriores. Em primeiro lugar, o comando CREATE RULE permite mais opções:

Em segundo lugar, não modificam a árvore de consulta original. Em vez disso, criam zero ou mais novas árvores de consulta, e podem descartar a original.

Cuidado

Em muitos casos, tarefas que poderiam ser executadas por regras para INSERT/UPDATE/DELETE são melhor realizadas com gatilhos. Os gatilhos são notacionalmente um pouco mais complicados, mas sua semântica é muito mais simples de entender. As regras tendem a ter resultados surpreendentes quando a consulta original contém funções voláteis: as funções voláteis podem ser executadas mais vezes do que o esperado no processo de execução das regras.

Além disso, existem alguns casos sem suporte por estes tipos de regras, incluindo cláusulas WITH na consulta original, e subconsultas (em SELECT) na lista SET de comandos UPDATE. Isto acontece porque copiar estas construções em uma consulta de regra resultaria em várias avaliações da subconsulta, contrariando a intenção expressa do autor da consulta.

39.4.1. Como funcionam as regras de atualização #

Tenha a sintaxe

CREATE [ OR REPLACE ] RULE nome AS ON evento
    TO nome_da_tabela [ WHERE condição ]
    DO [ ALSO | INSTEAD ] { NOTHING | comando | ( comando ; comando ... ) }

em mente. A seguir, regras de atualização significa regras definidas para INSERT, UPDATE, ou DELETE.

As regras de atualização são aplicadas pelo sistema de regras quando a relação de resultado e o tipo de comando de uma árvore de consulta são iguais ao objeto e evento fornecidos no comando CREATE RULE. Para regras de atualização, o sistema de regras cria uma lista de árvores de consulta. Inicialmente, a lista da árvore de consulta está vazia. Pode haver zero (palavra-chave NOTHING), uma ou várias ações. Para simplificar, vamos ver uma regra com uma ação. Esta regra pode ter uma qualificação, ou não, e pode ser INSTEAD, ou ALSO (o padrão).

O que é uma qualificação de regra? É uma restrição que diz quando as ações da regra devem ser executadas, e quando não. Esta qualificação só pode referenciar as pseudo-relações NEW e/ou OLD, que basicamente representam a relação dada como objeto (mas com um significado especial).

Portanto, temos três casos que produzem as seguintes árvores de consulta para uma regra de ação única.

Sem qualificação, com ALSO ou INSTEAD

a árvore de consulta da ação de regra, com adição da qualificação da árvore de consulta original

Qualificação fornecida e ALSO

a árvore de consulta da ação da regra, com a adição da qualificação da regra e a qualificação da árvore de consulta original

Qualificação fornecida e INSTEAD

a árvore de consulta da ação de regra, com a qualificação da consulta e a qualificação da árvore de consulta original; e acrescentando a árvore de consulta original com a qualificação inversa da regra

Por fim, se a regra for ALSO, a árvore de consulta original inalterada será adicionada à lista. Como apenas as regras INSTEAD qualificadas já adicionam a árvore de consulta original, termina-se com uma ou duas árvores de consulta de saída para uma regra com uma ação.

Para regras ON INSERT, a consulta original (se não for suprimida por INSTEAD) é feita antes de quaisquer ações adicionadas por regras. Isto permite que as ações vejam a(s) linha(s) inserida(s). Mas para as regras ON UPDATE e ON DELETE, a consulta original é executada após as ações adicionadas pelas regras. Isto garante que as ações possam ver as linhas a serem atualizadas ou excluídas; caso contrário, as ações não podem fazer nada, porque não encontram nenhuma linha que corresponda às suas qualificações.

As árvores de consulta geradas a partir de ações de regra são lançadas no sistema de reescrita novamente, e talvez sejam aplicadas mais regras, resultando em mais ou menos árvores de consulta. Portanto, as ações de uma regra devem ter um tipo de comando diferente, ou uma relação de resultado diferente da regra em si, caso contrário, este processo recursivo terminará em um ciclo infinito. (A expansão recursiva de uma regra será detectada e relatada como um erro.)

As árvores de consulta encontradas nas ações do catálogo do sistema pg_rewrite são apenas modelos. Uma vez que podem referenciar as entradas da tabela de intervalos para NEW e OLD, algumas substituições devem ser feitas antes que possam ser usadas. Para qualquer referência a NEW, a lista de destino da consulta original é procurada por uma entrada correspondente. Se encontrada, a expressão dessa entrada substituirá a referência. Caso contrário, NEW significa o mesmo que OLD (para o UPDATE), ou é substituído pelo valor nulo (para o INSERT). Qualquer referência a OLD é substituída por uma referência à entrada da tabela de intervalo que é a relação de resultado.

Após o sistema terminar de aplicar as regras de atualização, serão aplicadas as regras de visão à(s) árvore(s) de consulta produzida(s). As visões não podem inserir novas ações de atualização, portanto, não há necessidade de aplicar regras de atualização à saída da reescrita de visão.

39.4.1.1. Uma primeira regra passo a passo #

Digamos que queremos rastrear alterações na coluna cadarço_disp da relação cadarço_dados. Então vamos definir uma tabela de registro e uma regra que escreve condicionalmente uma entrada de registro quando um comando UPDATE é executado na tabela cadarço_dados.

CREATE TABLE cadarço_log (
    cadarço_nome    text,          -- cadarço alterado
    cadarço_disp    integer,       -- novo valor disponível
    log_quem        text,          -- quem alterou
    log_quando      timestamp      -- quando alterou
);

CREATE RULE log_cadarço AS ON UPDATE TO cadarço_dados
    WHERE NEW.cadarço_disp <> OLD.cadarço_disp
    DO INSERT INTO cadarço_log VALUES (
        NEW.cadarço_nome,
        NEW.cadarço_disp,
        current_user,
        current_timestamp
    );

Agora alguém executa

UPDATE cadarço_dados SET cadarço_disp = 6 WHERE cadarço_nome = 'cad7';

e consultando a tabela de registro encontramos:

SELECT * FROM cadarço_log;

 cadarço_nome | cadarço_disp | log_quem |         log_quando
--------------+--------------+----------+----------------------------
 cad7         |            6 | ana      | 2026-05-22 15:35:58.615653
(1 linha)

É o que se esperava. O que aconteceu por trás foi o seguinte. O analisador criou a árvore de consulta:

UPDATE cadarço_dados SET cadarço_disp = 6
  FROM cadarço_dados cadarço_dados
 WHERE cadarço_dados.cadarço_nome = 'cad7';

Existe a regra log_cadarço para ON UPDATE com a expressão de qualificação da regra

NEW.cadarço_disp <> OLD.cadarço_disp

e a ação

INSERT INTO cadarço_log VALUES (
       new.cadarço_nome, new.cadarço_disp,
       current_user, current_timestamp )
  FROM cadarço_dados new, cadarço_dados old;

(Isto parece um pouco estranho, já que normalmente não se pode escrever INSERT ... VALUES ... FROM. A cláusula FROM aqui é apenas para indicar que existem entradas da tabela de intervalos na árvore de consulta para new e old. Eles são necessários para poderem ser referenciados por variáveis na árvore de consulta do comando INSERT.)

A regra é uma regra ALSO qualificada, portanto, o sistema de regras deve retornar duas árvores de consulta: a ação da regra modificada, e a árvore de consulta original. No passo 1, a tabela de intervalos da consulta original é incorporada à árvore de consulta de ação da regra. Isto resulta em:

INSERT INTO cadarço_log VALUES (
       new.cadarço_nome, new.cadarço_disp,
       current_user, current_timestamp )
  FROM cadarço_dados new, cadarço_dados old,
       cadarço_dados cadarço_dados;

No passo 2, a qualificação da regra é adicionada, então o conjunto de resultados fica restrito às linhas onde cadarço_disp muda de valor:

INSERT INTO cadarço_log VALUES (
       new.cadarço_nome, new.cadarço_disp,
       current_user, current_timestamp )
  FROM cadarço_dados new, cadarço_dados old,
       cadarço_dados cadarço_dados
 WHERE new.cadarço_disp <> old.cadarço_disp;

(Isto parece ainda mais estranho, já que INSERT ... VALUES também não tem uma cláusula WHERE, mas o planejador e o executor não terão dificuldade com isto. Eles precisam oferecer suporte a esta mesma funcionalidade de qualquer maneira para INSERT ... SELECT.)

No passo 3, a qualificação da árvore de consulta original é adicionada, restringindo ainda mais o conjunto de resultados apenas às linhas que teriam sido tocadas pela consulta original:

INSERT INTO cadarço_log VALUES (
       new.cadarço_nome, new.cadarço_disp,
       current_user, current_timestamp )
  FROM cadarço_dados new, cadarço_dados old,
       cadarço_dados cadarço_dados
 WHERE new.cadarço_disp <> old.cadarço_disp
   AND cadarço_dados.cadarço_nome = 'cad7';

O passo 4 substitui as referências a NEW pelas entradas da lista de destino da árvore de consulta original, ou pelas referências de variáveis correspondentes da relação de resultado:

INSERT INTO cadarço_log VALUES (
       cadarço_dados.cadarço_nome, 6,
       current_user, current_timestamp )
  FROM cadarço_dados new, cadarço_dados old,
       cadarço_dados cadarço_dados
 WHERE 6 <> old.cadarço_disp
   AND cadarço_dados.cadarço_nome = 'cad7';

O passo 5 altera as referências OLD para referências da relação de resultado:

INSERT INTO cadarço_log VALUES (
       cadarço_dados.cadarço_nome, 6,
       current_user, current_timestamp )
  FROM cadarço_dados new, cadarço_dados old,
       cadarço_dados cadarço_dados
 WHERE 6 <> cadarço_dados.cadarço_disp
   AND cadarço_dados.cadarço_nome = 'cad7';

É isto. Como a regra é ALSO, também é gerada a árvore de consulta original. Resumindo, a saída do sistema de regras é uma lista de duas árvores de consulta correspondendo a estas declarações:

INSERT INTO cadarço_log VALUES (
       cadarço_dados.cadarço_nome, 6,
       current_user, current_timestamp )
  FROM cadarço_dados
 WHERE 6 <> cadarço_dados.cadarço_disp
   AND cadarço_dados.cadarço_nome = 'cad7';

UPDATE cadarço_dados SET cadarço_disp = 6
 WHERE cadarço_nome = 'cad7';

São executadas nesta ordem, sendo exatamente para isto que a regra foi criada.

As substituições e as qualificações adicionadas garantem que, se a consulta original fosse, digamos:

UPDATE cadarço_dados SET cadarço_cor = 'verde'
 WHERE cadarço_nome = 'cad7';

nenhuma entrada de registro seria escrita. Neste caso, a árvore de consulta original não contém uma entrada de lista de destino para cadarço_disp, portanto NEW.cadarço_disp será substituído por cadarço_dados.cadarço_disp. Assim, o comando extra gerado pela regra é:

INSERT INTO cadarço_log VALUES (
       cadarço_dados.cadarço_nome, cadarço_dados.cadarço_disp,
       current_user, current_timestamp )
  FROM cadarço_dados
 WHERE cadarço_dados.cadarço_disp <> cadarço_dados.cadarço_disp
   AND cadarço_dados.cadarço_nome = 'cad7';

e esta qualificação nunca será verdade.

Também funcionará se a consulta original modificar várias linhas. Então, se alguém executar o comando:

UPDATE cadarço_dados SET cadarço_disp = 0
 WHERE cadarço_cor = 'preto';

quatro linhas são de fato atualizadas (cad1, cad2, cad3, e cad4). Mas cad3 já tem cadarço_disp = 0. Neste caso, a qualificação das árvores de consulta originais é diferente, e isto resulta na árvore de consulta extra

INSERT INTO cadarço_log
SELECT cadarço_dados.cadarço_nome, 0,
       current_user, current_timestamp
  FROM cadarço_dados
 WHERE 0 <> cadarço_dados.cadarço_disp
   AND cadarço_dados.cadarço_cor = 'preto';

sendo gerada pela regra. Esta árvore de consulta certamente irá inserir três novas entradas de registro. E isto é inteiramente correto.

Aqui podemos ver porque é importante que a árvore de consulta original seja executada por último. Se o comando UPDATE tivesse sido executado primeiro, todas as linhas já teriam sido zeradas, então o comando INSERT que faz o registro não encontraria nenhuma linha onde 0 <> cadarço_dados.cadarço_disp.

39.4.2. Cooperação com visões #

Uma maneira simples de proteger as relações de visão da possibilidade mencionada de que alguém possa tentar executar os comandos INSERT, UPDATE, ou DELETE, diretamente sobre elas, é deixando estas árvores de consulta serem jogadas fora. Assim podem ser criadas as regras:

CREATE RULE sapato_ins_proteção AS ON INSERT TO sapato
    DO INSTEAD NOTHING;
CREATE RULE sapato_upd_proteção AS ON UPDATE TO sapato
    DO INSTEAD NOTHING;
CREATE RULE sapato_del_proteção AS ON DELETE TO sapato
    DO INSTEAD NOTHING;

Se alguém agora tentar fazer qualquer uma dessas operações na relação da visão sapato, o sistema de regras aplicará estas regras. Como as regras não têm ações, e são INSTEAD, a lista resultante de árvores de consulta ficará vazia, e toda a consulta se tornará nada, porque não há mais nada para ser otimizado ou executado após o sistema de regras estar concluído.

Uma maneira mais sofisticada de usar o sistema de regras é criando regras que reescrevem a árvore de consulta em uma que faça a operação correta nas tabelas reais. Para fazer isto na visão cadarço, são criadas as seguintes regras:

CREATE RULE cadarço_insert AS ON INSERT TO cadarço
    DO INSTEAD
    INSERT INTO cadarço_dados VALUES (
           NEW.cadarço_nome,
           NEW.cadarço_disp,
           NEW.cadarço_cor,
           NEW.cadarço_comp,
           NEW.cadarço_unid
    );

CREATE RULE cadarço_update AS ON UPDATE TO cadarço
    DO INSTEAD
    UPDATE cadarço_dados
       SET cadarço_nome = NEW.cadarço_nome,
           cadarço_disp = NEW.cadarço_disp,
           cadarço_cor  = NEW.cadarço_cor,
           cadarço_comp = NEW.cadarço_comp,
           cadarço_unid = NEW.cadarço_unid
     WHERE cadarço_nome = OLD.cadarço_nome;

CREATE RULE cadarço_delete AS ON DELETE TO cadarço
    DO INSTEAD
    DELETE FROM cadarço_dados
     WHERE cadarço_nome = OLD.cadarço_nome;

Se for desejado oferecer suporte a consultas com a cláusula RETURNING na visão, é necessário fazer com que as regras incluam cláusulas RETURNING que computam as linhas da visão. Isto é geralmente bastante trivial para visões em uma única tabela, mas é um pouco entediante para visões com junção como cadarço. Um exemplo para o caso de inserção é:

CREATE RULE cadarço_insert AS ON INSERT TO cadarço
    DO INSTEAD
    INSERT INTO cadarço_dados VALUES (
           NEW.cadarço_nome,
           NEW.cadarço_disp,
           NEW.cadarço_cor,
           NEW.cadarço_comp,
           NEW.cadarço_unid
    )
    RETURNING
           cadarço_dados.*,
           (SELECT cadarço_dados.cadarço_comp * u.unidade_fator
            FROM unidade u WHERE cadarço_dados.cadarço_unid = u.unidade_nome);

Note que esta regra oferece suporte as consultas INSERT e INSERT RETURNING na visão — a cláusula RETURNING é simplesmente ignorada para INSERT.

Note que na cláusula RETURNING de uma regra, OLD e NEW se referem às pseudo-relações adicionadas como entradas extras na tabela de intervalos da consulta reescrita, e não às linhas antigas/novas na relação de resultado. Assim, por exemplo, em uma regra que oferece suporte a consultas UPDATE nesta visão, se a cláusula RETURNING contivesse old.cadarço_nome, o nome antigo sempre seria retornado, independentemente da cláusula RETURNING na consulta da visão especificar OLD ou NEW, o que pode ser confuso. Para evitar esta confusão e permitir o retorno de valores antigos e novos em consultas na visão, a cláusula RETURNING na definição da regra deve se referir a entradas da relação de resultado como, por exemplo, cadarço_dados.cadarço_nome, sem especificar OLD ou NEW.

Agora vamos supor que, ocasionalmente, chegue à loja um pacote de cadarços, e uma grande lista de peças junto. Mas não se deseja atualizar manualmente a visão cadarço todas às vezes. Em vez disso, são definidas duas pequenas tabelas: uma onde se pode inserir os itens da lista de peças, e outra com um truque especial. Os comandos de criação para estas tabelas incluindo a regra são:

CREATE TABLE cadarço_chegada (
    chegada_nome    text,
    chegada_quant   integer
);

CREATE TABLE cadarço_ok (
    ok_nome     text,
    ok_quant    integer
);

CREATE RULE cadarço_ok_ins AS ON INSERT TO cadarço_ok
    DO INSTEAD
    UPDATE cadarço
       SET cadarço_disp = cadarço_disp + NEW.ok_quant
     WHERE cadarço_nome = NEW.ok_nome;

Agora pode-se preencher a tabela cadarço_chegada com os dados da lista de peças:

SELECT * FROM cadarço_chegada;

 chegada_nome | chegada_quant
--------------+---------------
 cad3         |            10
 cad6         |            20
 cad8         |            20
(3 linhas)

Vamos dar uma olhada rápida nos dados correntes:

SELECT * FROM cadarço ORDER BY cadarço_nome;

 cadarço_nome | cadarço_disp | cadarço_cor | cadarço_comp | cadarço_unid | cadarço_comp_cm
--------------+--------------+-------------+--------------+--------------+-----------------
 cad1         |            5 | preto       |           80 | cm           |              80
 cad2         |            6 | preto       |          100 | cm           |             100
 cad3         |            0 | preto       |           35 | pol          |            88.9
 cad4         |            8 | preto       |           40 | pol          |           101.6
 cad5         |            4 | marrom      |            1 | m            |             100
 cad6         |            0 | marrom      |          0.9 | m            |              90
 cad7         |            6 | verde       |           60 | cm           |              60
 cad8         |            1 | marrom      |           40 | pol          |           101.6
(8 linhas)

Agora vamos adicionar os cadarços chegados com

INSERT INTO cadarço_ok SELECT * FROM cadarço_chegada;

e conferir os resultados:

SELECT * FROM cadarço ORDER BY cadarço_nome;

 cadarço_nome | cadarço_disp | cadarço_cor | cadarço_comp | cadarço_unid | cadarço_comp_cm
--------------+--------------+-------------+--------------+--------------+-----------------
 cad1         |            5 | preto       |           80 | cm           |              80
 cad2         |            6 | preto       |          100 | cm           |             100
 cad3         |           10 | preto       |           35 | pol          |            88.9
 cad4         |            8 | preto       |           40 | pol          |           101.6
 cad5         |            4 | marrom      |            1 | m            |             100
 cad6         |           20 | marrom      |          0.9 | m            |              90
 cad7         |            6 | verde       |           60 | cm           |              60
 cad8         |           21 | marrom      |           40 | pol          |           101.6
(8 linhas)

SELECT * FROM cadarço_log;

 cadarço_nome | cadarço_disp | log_quem |         log_quando
--------------+--------------+----------+----------------------------
 cad7         |            6 | ana      | 2026-05-22 15:56:59.506778
 cad3         |           10 | ana      | 2026-05-22 16:00:36.987003
 cad6         |           20 | ana      | 2026-05-22 16:00:36.987003
 cad8         |           21 | ana      | 2026-05-22 16:00:36.987003
(4 linhas)

É um longo caminho desde INSERT ... SELECT até estes resultados. E a descrição da transformação da árvore de consulta será a última neste capítulo. Primeiro, há a saída do analisador:

INSERT INTO cadarço_ok
SELECT cadarço_chegada.chegada_nome, cadarço_chegada.chegada_quant
  FROM cadarço_chegada cadarço_chegada, cadarço_ok cadarço_ok;

Agora a primeira regra cadarço_ok_ins é aplicada, transformando isto em

UPDATE cadarço
   SET cadarço_disp = cadarço.cadarço_disp + cadarço_chegada.chegada_quant
  FROM cadarço_chegada cadarço_chegada, cadarço_ok cadarço_ok,
       cadarço_ok old, cadarço_ok new,
       cadarço cadarço
 WHERE cadarço.cadarço_nome = cadarço_chegada.chegada_nome;

jogando fora o comando INSERT original em cadarço_ok. Esta consulta reescrita é passada para o sistema de regras novamente, e a segunda regra aplicada cadarço_update produz:

UPDATE cadarço_dados
   SET cadarço_nome = cadarço.cadarço_nome,
       cadarço_disp = cadarço.cadarço_disp + cadarço_chegada.chegada_quant,
       cadarço_cor = cadarço.cadarço_cor,
       cadarço_comp = cadarço.cadarço_comp,
       cadarço_unid = cadarço.cadarço_unid
  FROM cadarço_chegada cadarço_chegada, cadarço_ok cadarço_ok,
       cadarço_ok old, cadarço_ok new,
       cadarço cadarço, cadarço old,
       cadarço new, cadarço_dados cadarço_dados
 WHERE cadarço.cadarço_nome = cadarço_chegada.chegada_nome
   AND cadarço_dados.cadarço_nome = cadarço.cadarço_nome;

Novamente, é uma regra INSTEAD e a árvore de consulta anterior é descartada. Note que esta consulta ainda utiliza a visão cadarço. Mas o sistema de regras não termina com esta etapa, então ele continua e aplica a regra _RETURN, obtendo:

UPDATE cadarço_dados
   SET cadarço_nome = s.cadarço_nome,
       cadarço_disp = s.cadarço_disp + cadarço_chegada.chegada_quant,
       cadarço_cor = s.cadarço_cor,
       cadarço_comp = s.cadarço_comp,
       cadarço_unid = s.cadarço_unid
  FROM cadarço_chegada cadarço_chegada, cadarço_ok cadarço_ok,
       cadarço_ok old, cadarço_ok new,
       cadarço cadarço, cadarço old,
       cadarço new, cadarço_dados cadarço_dados,
       cadarço old, cadarço new,
       cadarço_dados s, unidade u
 WHERE s.cadarço_nome = cadarço_chegada.chegada_nome
   AND cadarço_dados.cadarço_nome = s.cadarço_nome;

Por fim, a regra log_cadarço é aplicada, produzindo a árvore de consulta extra:

INSERT INTO cadarço_log
SELECT s.cadarço_nome,
       s.cadarço_disp + cadarço_chegada.chegada_quant,
       current_user,
       current_timestamp
  FROM cadarço_chegada cadarço_chegada, cadarço_ok cadarço_ok,
       cadarço_ok old, cadarço_ok new,
       cadarço cadarço, cadarço old,
       cadarço new, cadarço_dados cadarço_dados,
       cadarço old, cadarço new,
       cadarço_dados s, unidade u,
       cadarço_dados old, cadarço_dados new
       cadarço_log cadarço_log
 WHERE s.cadarço_nome = cadarço_chegada.chegada_nome
   AND cadarço_dados.cadarço_nome = s.cadarço_nome
   AND (s.cadarço_disp + cadarço_chegada.chegada_quant) <> s.cadarço_disp;

Depois disso, o sistema de regras fica sem regras, e retorna as árvores de consulta geradas.

Portanto, terminamos com duas árvores de consulta finais que são equivalentes às instruções SQL:

INSERT INTO cadarço_log
SELECT s.cadarço_nome,
       s.cadarço_disp + cadarço_chegada.chegada_quant,
       current_user,
       current_timestamp
  FROM cadarço_chegada cadarço_chegada, cadarço_dados cadarço_dados,
       cadarço_dados s
 WHERE s.cadarço_nome = cadarço_chegada.chegada_nome
   AND cadarço_dados.cadarço_nome = s.cadarço_nome
   AND s.cadarço_disp + cadarço_chegada.chegada_quant <> s.cadarço_disp;

UPDATE cadarço_dados
   SET cadarço_disp = cadarço_dados.cadarço_disp + cadarço_chegada.chegada_quant
  FROM cadarço_chegada cadarço_chegada,
       cadarço_dados cadarço_dados,
       cadarço_dados s
 WHERE s.cadarço_nome = cadarço_chegada.cadarço_nome
   AND cadarço_dados.cadarço_nome = s.cadarço_nome;

O resultado é que os dados vindos de uma relação inseridos em outra, transformados em atualizações em uma terceira, transformados em atualização em uma quarta, mais o registro dessa atualização final em uma quinta, são reduzidos a duas consultas.

Há um pequeno detalhe que é um pouco feio. Olhando para as duas consultas, verifica-se que a relação cadarço_dados aparece duas vezes na tabela de intervalos, onde definitivamente poderia ser reduzida a uma vez. O planejador não trata isto, portanto, o plano de execução para a saída dos sistemas de regras do INSERT será

Nested Loop
  ->  Merge Join
        ->  Seq Scan
              ->  Sort
                    ->  Seq Scan on s
        ->  Seq Scan
              ->  Sort
                    ->  Seq Scan on cadarço_chegada
  ->  Seq Scan on cadarço_dados

enquanto omitindo a entrada da tabela de intervalos extra resultaria em

Merge Join
  ->  Seq Scan
        ->  Sort
              ->  Seq Scan on s
  ->  Seq Scan
        ->  Sort
              ->  Seq Scan on cadarço_chegada

que produz exatamente as mesmas entradas na tabela de registro. Assim, o sistema de regras gerou uma varredura extra na tabela cadarço_dados, que absolutamente não é necessária. E a mesma varredura redundante é feita mais uma vez no UPDATE. Mas foi um trabalho muito difícil tornar tudo isto possível.

Agora será feita uma demonstração final do sistema de regras do PostgreSQL e seu poder. Digamos que são adicionados alguns cadarços com cores extraordinárias ao banco de dados (além do verde anterior):

INSERT INTO cadarço VALUES ('cad9', 0, 'rosa', 35.0, 'pol', 0.0);
INSERT INTO cadarço VALUES ('cad10', 1000, 'magenta', 40.0, 'pol', 0.0);

Deseja-se criar uma visão para verificar quais entradas de cadarço não correspondem a nenhuma cor preferida de sapato. A visão para isto é:

CREATE VIEW cadarço_descasado AS
    SELECT * FROM cadarço WHERE NOT EXISTS
        (SELECT sapato_nome FROM sapato WHERE cadarço_cor_pref = cadarço_cor);

Sua saída é:

SELECT * FROM cadarço_descasado;

 cadarço_nome | cadarço_disp | cadarço_cor | cadarço_comp | cadarço_unid | cadarço_comp_cm
--------------+--------------+-------------+--------------+--------------+-----------------
 cad9         |            0 | rosa        |           35 | pol          |            88.9
 cad10        |         1000 | magenta     |           40 | pol          |           101.6
(2 linhas)

Agora deseja-se configurar para que os cadarços incompatíveis que não estão em estoque sejam excluídos do banco de dados. Para tornar um pouco mais difícil para o PostgreSQL, a exclusão não será feita diretamente. Em vez disso, será criada a visão

CREATE VIEW cadarço_pode_excluir AS
    SELECT * FROM cadarço_descasado WHERE cadarço_disp = 0;

executada como

DELETE FROM cadarço WHERE EXISTS
    (SELECT * FROM cadarço_pode_excluir
             WHERE cadarço_nome = cadarço.cadarço_nome);

resultando em:

CREATE COLLATION "pt-natural" (provider = icu, locale = 'pt-u-kn-true');

SELECT * FROM cadarço ORDER BY cadarço_nome COLLATE "pt-natural";

 cadarço_nome | cadarço_disp | cadarço_cor | cadarço_comp | cadarço_unid | cadarço_comp_cm
--------------+--------------+-------------+--------------+--------------+-----------------
 cad1         |            5 | preto       |           80 | cm           |              80
 cad2         |            6 | preto       |          100 | cm           |             100
 cad3         |           10 | preto       |           35 | pol          |            88.9
 cad4         |            8 | preto       |           40 | pol          |           101.6
 cad5         |            4 | marrom      |            1 | m            |             100
 cad6         |           20 | marrom      |          0.9 | m            |              90
 cad7         |            6 | verde       |           60 | cm           |              60
 cad8         |           21 | marrom      |           40 | pol          |           101.6
 cad10        |         1000 | magenta     |           40 | pol          |           101.6
(9 linhas)

Um comando DELETE em uma visão, com uma qualificação de subconsulta que no total usa 4 visões aninhadas/juntadas, onde uma delas possui uma qualificação de subconsulta contendo uma visão, e onde são usadas colunas de visão calculadas, é reescrito em uma única árvore de consulta que exclui os dados solicitados de uma tabela real.

Provavelmente, existem apenas algumas poucas situações no mundo real em que tal construção é necessária. Mas é gratificante saber que funciona.