O CTE é mais lento porque deve ser executado inalterado (por meio de uma varredura CTE).
A TFM (seção 7.8.2) declara: As instruções de modificação de dados em WITH são executadas exatamente uma vez, e sempre até a conclusão, independentemente de a consulta primária ler toda (ou alguma) sua saída. Observe que isso é diferente da regra para SELECT em WITH:conforme declarado na seção anterior, a execução de um SELECT é transportado apenas até onde a consulta primária exige sua saída.
É, portanto, uma barreira de otimização; para o otimizador, não é permitido desmontar o CTE, mesmo que resulte em um plano mais inteligente com os mesmos resultados.
A solução CTE pode ser refatorada em uma subconsulta unida (semelhante à tabela temporária na pergunta). No postgres, uma subconsulta unida geralmente é mais rápida que a variante EXISTS(), hoje em dia.
DELETE FROM customer del
USING ( SELECT id
, row_number() over(partition by uuid order by created_date desc)
as rn
FROM customer
) sub
WHERE sub.id = del.id
AND sub.rn > 1
;
Outra maneira é usar um
TEMP VIEW
. Isso é sintaticamente equivalente à temp table
caso, mas semanticamente equivalente ao formulário de subconsulta unida (eles produzem exatamente o mesmo plano de consulta, pelo menos neste caso). Isso ocorre porque o otimizador do Postgres desmonta a visualização e a combina com a consulta principal (pull-up ). Você pode ver uma view
como uma espécie de macro no PG. CREATE TEMP VIEW targets
AS SELECT id
, row_number() over(partition by uuid ORDER BY created_date DESC) AS rn
FROM customer;
EXPLAIN
DELETE FROM customer
WHERE id IN ( SELECT id
FROM targets
WHERE rn > 1
);
[ATUALIZADO:Eu estava errado sobre os CTEs precisarem ser sempre executados até a conclusão, o que é apenas o caso dos CTEs de modificação de dados]