← Back to list

Expurgando / Deletando linhas duplicadas no PostgreSQL

query para limpar linhas 100% duplicadas em tabelas

Gabriel Figueiredo · 2026-01-16 17:20 · 1 claps · 1.8 min read
#sql #postgresql #dba #tuning #sql-tips
Open on Medium ↗

Expurgando / Deletando linhas duplicadas no PostgreSQL

query para limpar linhas 100% duplicadas em tabelas

Galera, post bem rapidinho sobre um caso que aconteceu essa semana para expurgo de uma tabela.

O cliente tinha uma tabela sem CONSTRAINT e que continha diversas linhas duplicadas, nesse caso eu poderia fazer um rank usando ROW_NUMBER() para pegar os dados, copiar para outra tabela e depois renomear em um horário de manutenção, mas achei essa solução muito feia. Logo, lembrei do ctid (current tupple identifier ) que é uma coluna interna do postgres que identifica fisicamente aonde cada tupla está no disco ( pagina, posição)

-- exemplo 
select ctid,* from tabela;

-- Saida:
--
-- ctid | col1 | col2
-- (0,1)|'oi'  | 123
-- (0,2)|'alo' | 345

Então usei a mesma lógica para criar essa query que rankeia a linha e mantém sempre 1 registro ( provavelmente o mais antigo )

DELETE FROM tb_name
WHERE ctid IN (
    SELECT ctid
    FROM (
        SELECT ctid,
               ROW_NUMBER() OVER (
                   PARTITION BY col1,col2 -- pode botar quantas preciso
                   ORDER BY ctid
               ) rn
        FROM tb_name
    ) t
    WHERE rn > 1
);

Podemos ir um pouco além e criar uma procedure para agendar via cron

-- DROP PROCEDURE public.cleanup_table();

CREATE OR REPLACE PROCEDURE hml.cleanup_morpheus_people(IN tb_name TEXT)
 LANGUAGE plpgsql
AS $procedure$
DECLARE
    v_table_name    text := tb_name;

    v_start_time    timestamptz := clock_timestamp();
    v_end_time      timestamptz;

    v_rows_before   bigint := 0;
    v_rows_after    bigint := 0;
    v_rows_deleted  bigint := 0;

    v_error_message text;
    v_sql           text;
BEGIN
    ----------------------------------------------------------------
    -- COUNT BEFORE
    ----------------------------------------------------------------
    EXECUTE format('SELECT count(*) FROM %s', v_table_name)
    INTO v_rows_before;

----------------------------------------------------------------
-- DELETE DUPLICADOS )
----------------------------------------------------------------
    v_sql := format($SQL$
         DELETE FROM %s
        WHERE ctid IN (
            SELECT ctid
            FROM (
                SELECT ctid,
                   ROW_NUMBER() OVER (
                       PARTITION BY col1,col2 -- pode botar quantas preciso
                       ORDER BY ctid
                   ) AS rn
                FROM %s
            ) t
            WHERE rn > 1
        )
    $SQL$, v_table_name, v_table_name);

    EXECUTE v_sql;
    GET DIAGNOSTICS v_rows_deleted = ROW_COUNT;

    EXECUTE format('SELECT count(*) FROM %s', v_table_name)
    INTO v_rows_after;

    v_end_time := clock_timestamp();

    ----------------------------------------------------------------
    -- SUCCESS LOG
    ----------------------------------------------------------------
    INSERT INTO public.cleanup_table_log (
        table_name,
        executed_by,
        start_time,
        end_time,
        duration,
        rows_before,
        rows_deleted,
        rows_after,
        status,
        error_message,
        notes
    )
    VALUES (
        v_table_name,
        current_user,
        v_start_time,
        v_end_time,
        v_end_time - v_start_time,
        v_rows_before,
        v_rows_deleted,
        v_rows_after,
        'SUCCESS',
        NULL,
        'limpando linhas duplicadas'
    );

EXCEPTION
    WHEN OTHERS THEN
        v_end_time := clock_timestamp();
        v_error_message := SQLERRM;

        ----------------------------------------------------------------
        -- FAIL LOG (ISOLADO)
        ----------------------------------------------------------------
        BEGIN
            INSERT INTO hml.cleanup_morpheus_log (
                table_name,
                executed_by,
                start_time,
                end_time,
                duration,
                rows_before,
                rows_deleted,
                rows_after,
                status,
                error_message,
                notes
            )
            VALUES (
                v_table_name,
                current_user,
                v_start_time,
                v_end_time,
                v_end_time - v_start_time,
                COALESCE(v_rows_before, 0),
                0,
                NULL,
                'FAIL',
                v_error_message,
                'Erro durante cleanup'
            );
        EXCEPTION
            WHEN OTHERS THEN
                RAISE NOTICE 'Falha ao registrar log de erro: %', SQLERRM;
        END;

        RETURN;
END;
$procedure$
;

É isso pessoal, até a próxima 👋


메타데이터
post_id
55f27d3e85b6
slug
expurgando-deletando-linhas-duplicadas-55f27d3e85b6
url
https://medium.com/@gabrieloliveira-dba/expurgando-deletando-linhas-duplicadas-55f27d3e85b6
canonical_url
https://medium.com/@gabrieloliveira-dba/expurgando-deletando-linhas-duplicadas-55f27d3e85b6
author_url
https://medium.com/@gabrieloliveira-dba
status
ok
fetched_at
2026-06-09 15:37:30