Truncando todas as tabelas em um banco de dados Postgres

155

Eu regularmente preciso excluir todos os dados do meu banco de dados PostgreSQL antes de uma reconstrução. Como eu faria isso diretamente no SQL?

No momento, consegui criar uma instrução SQL que retorne todos os comandos que preciso executar:

SELECT 'TRUNCATE TABLE ' ||  tablename || ';' FROM pg_tables WHERE tableowner='MYUSER';

Mas não consigo ver uma maneira de executá-los programaticamente depois que os tiver.

Sig
fonte

Respostas:

226

FrustratedWithFormsDesigner está correto, PL / pgSQL pode fazer isso. Aqui está o script:

CREATE OR REPLACE FUNCTION truncate_tables(username IN VARCHAR) RETURNS void AS $$
DECLARE
    statements CURSOR FOR
        SELECT tablename FROM pg_tables
        WHERE tableowner = username AND schemaname = 'public';
BEGIN
    FOR stmt IN statements LOOP
        EXECUTE 'TRUNCATE TABLE ' || quote_ident(stmt.tablename) || ' CASCADE;';
    END LOOP;
END;
$$ LANGUAGE plpgsql;

Isso cria uma função armazenada (você precisa fazer isso apenas uma vez) que pode ser usada posteriormente:

SELECT truncate_tables('MYUSER');
Henning
fonte
1
Tive que rejeitar um pouco, mas depois disso funcionou como um encanto! Eu nunca usei o plpgsql antes, então isso me levaria anos. Obrigado! Para quem precisar, adicionei o código que acabei usando na parte inferior deste post.
Sig
Desculpe, eu provavelmente estava pensando no Oracle PL / SQL :( Corrigi o erro de sintaxe no meu código acima.
Henning
1
você também pode mover a instrução SELECT diretamente para o loop FOR. DECLARE r RECORD;então for loop: FOR r IN SELECT tablename FROM pg_tables LOOP
Michael Buen
6
Eu adicionaria CASCADE ao TRUNCATE TABLE
Bogdan Gusiev
3
AMD!! Acabei de truncar todas as minhas tabelas no esquema "público" .... os pls adicionam outro parâmetro de "esquema" para que a função trunque tabelas apenas no esquema fornecido!
Roneo
95

Cursores explícitos raramente são necessários no plpgsql. Use o cursor implícito mais simples e rápido de um FORloop:

Nota: Como os nomes das tabelas não são exclusivos por banco de dados, é necessário qualificar os nomes das tabelas para garantir o esquema. Além disso, limito a função ao esquema padrão 'público'. Adapte-se às suas necessidades, mas certifique-se de excluir os esquemas do sistema pg_*e information_schema.

Tenha muito cuidado com essas funções. Eles danificam seu banco de dados. Eu adicionei um dispositivo de segurança infantil. Comente a RAISE NOTICElinha e descomente EXECUTEpara preparar a bomba ...

CREATE OR REPLACE FUNCTION f_truncate_tables(_username text)
  RETURNS void AS
$func$
DECLARE
   _tbl text;
   _sch text;
BEGIN
   FOR _sch, _tbl IN 
      SELECT schemaname, tablename
      FROM   pg_tables
      WHERE  tableowner = _username
      AND    schemaname = 'public'
   LOOP
      RAISE NOTICE '%',
      -- EXECUTE  -- dangerous, test before you execute!
         format('TRUNCATE TABLE %I.%I CASCADE', _sch, _tbl);
   END LOOP;
END
$func$ LANGUAGE plpgsql;

format()requer o Postgres 9.1 ou posterior. Nas versões mais antigas, concatenar a cadeia de consulta da seguinte maneira:

'TRUNCATE TABLE ' || quote_ident(_sch) || '.' || quote_ident(_tbl)  || ' CASCADE';

Comando único, sem loop

Como podemos criar TRUNCATEvárias tabelas ao mesmo tempo, não precisamos de nenhum cursor ou loop:

Agregue todos os nomes de tabela e execute uma única instrução. Mais simples, mais rápido:

CREATE OR REPLACE FUNCTION f_truncate_tables(_username text)
  RETURNS void AS
$func$
BEGIN
   RAISE NOTICE '%', 
   -- EXECUTE  -- dangerous, test before you execute!
  (SELECT 'TRUNCATE TABLE '
       || string_agg(format('%I.%I', schemaname, tablename), ', ')
       || ' CASCADE'
   FROM   pg_tables
   WHERE  tableowner = _username
   AND    schemaname = 'public'
   );
END
$func$ LANGUAGE plpgsql;

Ligar:

SELECT truncate_tables('postgres');

Consulta refinada

Você nem precisa de uma função. No Postgres 9.0+, você pode executar comandos dinâmicos em uma DOinstrução. E no Postgres 9.5+, a sintaxe pode ser ainda mais simples:

DO
$func$
BEGIN
   RAISE NOTICE '%', 
   -- EXECUTE
   (SELECT 'TRUNCATE TABLE ' || string_agg(oid::regclass::text, ', ') || ' CASCADE'
    FROM   pg_class
    WHERE  relkind = 'r'  -- only tables
    AND    relnamespace = 'public'::regnamespace
   );
END
$func$;

Sobre a diferença entre pg_class, pg_tablese information_schema.tables:

Sobre regclasse citou nomes de tabelas:

Para uso repetido

Crie um banco de dados "modelo" (vamos chamá-lo my_template) com sua estrutura de baunilha e todas as tabelas vazias. Em seguida, passe por um ciclo DROP/CREATE DATABASE :

DROP DATABASE mydb;
CREATE DATABASE mydb TEMPLATE my_template;

Isso é extremamente rápido , porque o Postgres copia toda a estrutura no nível do arquivo. Não há problemas de simultaneidade ou outras sobrecargas que o tornam mais lento.

Se conexões simultâneas impedirem que você elimine o banco de dados, considere:

Erwin Brandstetter
fonte
1
Vale a pena notar que esta última função limpou TODOS os bancos de dados. Não é apenas o atualmente conectado .... sim ... me chame de ingênuo, mas isso realmente não ficou claro neste post.
Amalgovinus
@Amalgovinus: Qual última função? Nenhuma das funções na minha resposta toca em nada fora do banco de dados atual (exceto DROP DATABASE mydb, obviamente). Você está confundindo esquemas com bancos de dados, talvez?
Erwin Brandstetter
3
@Amalgovinus: Não, isso é impossível. O DOcomando (como qualquer outra instrução SQL) é executado exclusivamente no banco de dados atual . O Postgres não tem como acessar outros bancos de dados na mesma transação. Você precisaria usar dblink ou FDW para fazer isso. Mas não afeta todos os esquemas no banco de dados atual - a menos que você adicionar WHERE t.schemaname = 'public'para restringir o efeito a um esquema específico, neste caso particular.
Erwin Brandstetter
1
Muito bom saber sobre esses modelos. Isso pode ser útil mesmo em cenários de testes automatizados, onde pode ser necessária uma redefinição / preparação do banco de dados.
Hbobenicio 16/03
3
Obrigado pela ótima resposta, estou usando "Comando único, sem loop", que retorna o comando TRUNCATE, como devo executá-lo?
Mahyar 5/11
40

Se eu tiver que fazer isso, simplesmente criarei um esquema sql do banco de dados atual, depois solto e cria o banco de dados e carrego o banco de dados com o esquema sql.

Abaixo estão as etapas envolvidas:

1) Criar dump de esquema do banco de dados ( --schema-only)

pg_dump mydb -s > schema.sql

2) Soltar banco de dados

drop database mydb;

3) Criar banco de dados

create database mydb;

4) Esquema de importação

psql mydb < schema.sql

Sandip Ransing
fonte
9

Nesse caso, provavelmente seria melhor ter apenas um banco de dados vazio usado como modelo e, quando precisar atualizar, descarte o banco de dados existente e crie um novo a partir do modelo.

Scott Bailey
fonte
3

Você pode fazer isso com o bash também:

#!/bin/bash
PGPASSWORD='' psql -h 127.0.0.1 -Upostgres sng --tuples-only --command "SELECT 'TRUNCATE TABLE ' || schemaname || '.' ||  tablename || ';' FROM pg_tables WHERE schemaname in ('cms_test', 'ids_test', 'logs_test', 'sps_test');" | 
tr "\\n" " " | 
xargs -I{} psql -h 127.0.0.1 -Upostgres sng --command "{}"

Você precisará ajustar nomes, senhas e nomes de usuário de esquemas para corresponder aos seus esquemas.

Simão
fonte
3

AUTO_INCREMENTVersão de limpeza :

CREATE OR REPLACE FUNCTION truncate_tables(username IN VARCHAR) RETURNS void AS $$
DECLARE
    statements CURSOR FOR
        SELECT tablename FROM pg_tables
        WHERE tableowner = username AND schemaname = 'public';
BEGIN
    FOR stmt IN statements LOOP
        EXECUTE 'TRUNCATE TABLE ' || quote_ident(stmt.tablename) || ' CASCADE;';

        IF EXISTS (
            SELECT column_name 
            FROM information_schema.columns 
            WHERE table_name=quote_ident(stmt.tablename) and column_name='id'
        ) THEN
           EXECUTE 'ALTER SEQUENCE ' || quote_ident(stmt.tablename) || '_id_seq RESTART WITH 1';
        END IF;

    END LOOP;
END;
$$ LANGUAGE plpgsql;
RomanGorbatko
fonte
3

Pessoal, a maneira melhor e limpa é:

1) Criar dump de esquema do banco de dados (--schema-only) pg_dump mydb -s> schema.sql

2) Soltar banco de dados soltar banco de dados mydb;

3) Criar banco de dados criar banco de dados mydb;

4) Importar esquema psql mydb <schema.sql

É trabalho pra mim!

Tenha um bom dia. Hiram Walker

Hiram Walker
fonte
2

Se você pode usar o psql, pode usar o \gexeccomando meta para executar a saída da consulta;

SELECT
    format('TRUNCATE TABLE %I.%I', ns.nspname, c.relname)
  FROM pg_namespace ns 
  JOIN pg_class c ON ns.oid = c.relnamespace
  JOIN pg_roles r ON r.oid = c.relowner
  WHERE
    ns.nspname = 'table schema' AND                               -- add table schema criteria 
    r.rolname = 'table owner' AND                                 -- add table owner criteria
    ns.nspname NOT IN ('pg_catalog', 'information_schema') AND    -- exclude system schemas
    c.relkind = 'r' AND                                           -- tables only
    has_table_privilege(c.oid, 'TRUNCATE')                        -- check current user has truncate privilege
  \gexec 

Observe que \gexecé introduzido na versão 9.6

Sahap Asci
fonte
1

Para remover os dados e preservar as estruturas de tabela no pgAdmin, você pode:

  • Clique com o botão direito do mouse em banco de dados -> backup, selecione "Somente esquema"
  • Solte o banco de dados
  • Crie um novo banco de dados e nomeie-o como o antigo
  • Clique com o botão direito do mouse no novo banco de dados -> restaurar -> selecione o backup, selecione "Somente esquema"
mYnDstrEAm
fonte