Truncar todas las tablas en una base de datos Postgres

155

Regularmente necesito eliminar todos los datos de mi base de datos PostgreSQL antes de una reconstrucción. ¿Cómo haría esto directamente en SQL?

Por el momento, he logrado crear una declaración SQL que devuelve todos los comandos que necesito ejecutar:

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

Pero no puedo ver una manera de ejecutarlos mediante programación una vez que los tengo.

Sig
fuente

Respuestas:

226

FrustratedWithFormsDesigner es correcto, PL / pgSQL puede hacer esto. Aquí está el guión:

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;

Esto crea una función almacenada (debe hacer esto solo una vez) que luego puede usar así:

SELECT truncate_tables('MYUSER');
Henning
fuente
1
Tuve que volver a tocar un poco, pero después de eso funcionó a las mil maravillas. Nunca he usado plpgsql antes, así que esto me habría llevado años. ¡Gracias! Para cualquiera que lo necesite, he agregado el código que terminé usando al final de esta publicación.
Sig
Lo siento, probablemente estaba pensando en Oracle PL / SQL :( Solucioné el error de sintaxis en mi código anterior.
Henning
1
También puede mover la instrucción SELECT directamente al bucle FOR. DECLARE r RECORD;luego para bucle: FOR r IN SELECT tablename FROM pg_tables LOOP
Michael Buen
66
Añadiría CASCADE a TRUNCATE TABLE
Bogdan Gusiev
3
¡¡DIOS MIO!! Acabo de truncar todas mis tablas en el esquema "público" ... ¡por favor agregue otro parámetro de "esquema" para que la función trunque las tablas solo en el esquema que se proporciona!
roneo
95

Los cursores explícitos rara vez se necesitan en plpgsql. Use el cursor implícito más simple y rápido de un FORbucle:

Nota: Dado que los nombres de las tablas no son únicos por base de datos, debe asegurarse de que los nombres de las tablas califiquen en el esquema. Además, limito la función al esquema predeterminado 'public'. Adaptarse a sus necesidades, pero asegúrese de excluir los esquemas del sistema pg_*y information_schema.

Ten mucho cuidado con estas funciones. Atacan tu base de datos. Agregué un dispositivo de seguridad para niños. Comenta la RAISE NOTICElínea y descomenta EXECUTEpara preparar la 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()requiere Postgres 9.1 o posterior. En versiones anteriores concatena la cadena de consulta de esta manera:

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

Comando único, sin bucle

Como podemos hacer TRUNCATEvarias tablas a la vez, no necesitamos ningún cursor o bucle:

Agregue todos los nombres de tabla y ejecute una sola declaración. Más simple, más 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;

Llamada:

SELECT truncate_tables('postgres');

Consulta refinada

Ni siquiera necesitas una función. En Postgres 9.0+ puedes ejecutar comandos dinámicos en una DOdeclaración. Y en Postgres 9.5+ la sintaxis puede ser aún más simple:

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 la diferencia entre pg_class, pg_tablesy information_schema.tables:

Sobre regclassy nombres de tablas citadas:

Para uso repetido

Cree una base de datos de "plantilla" (nombremos my_template) con su estructura vainilla y todas las tablas vacías. Luego pase por un ciclo DROP/CREATE DATABASE :

DROP DATABASE mydb;
CREATE DATABASE mydb TEMPLATE my_template;

Esto es extremadamente rápido , porque Postgres copia toda la estructura en el nivel del archivo. No hay problemas de concurrencia u otros gastos generales que lo retrasen.

Si las conexiones concurrentes evitan que se caiga la base de datos, considere:

Erwin Brandstetter
fuente
1
Vale la pena señalar que esta última función borró TODAS las bases de datos. No solo el conectado actualmente ... sí ... llámame ingenuo, pero eso realmente no estaba claro en esta publicación.
Amalgovinus
@Amalgovinus: ¿Qué última función? Ninguna de las funciones en mi respuesta toca nada fuera de la base de datos actual (excepto DROP DATABASE mydb, obviamente). ¿Estás confundiendo esquemas con bases de datos, tal vez?
Erwin Brandstetter
3
@Amalgovinus: No, eso es imposible. El DOcomando (como cualquier otra instrucción SQL) se ejecuta exclusivamente en la base de datos actual . Postgres no tiene forma de acceder a otras bases de datos en la misma transacción. Tendría que usar dblink o FDW para hacer eso. Pero afecta a todos los esquemas en la base de datos actual, a menos que agregue WHERE t.schemaname = 'public'para restringir el efecto a un esquema particular en este caso particular.
Erwin Brandstetter
1
Realmente bueno saber sobre esas plantillas. Esto puede ser útil incluso en escenarios de pruebas automatizadas, donde puede ser necesario un reinicio / preparación de la base de datos.
hbobenicio
3
Gracias por una excelente respuesta, estoy usando "Comando único, sin bucle" que devuelve el comando TRUNCATE, ¿cómo debo ejecutarlo?
Mahyar
40

Si tengo que hacer esto, simplemente crearé un esquema sql de db actual, luego soltaré y crearé db, luego cargaré db con el esquema sql.

A continuación se detallan los pasos involucrados:

1) Crear un volcado de esquema de la base de datos ( --schema-only)

pg_dump mydb -s > schema.sql

2) Eliminar base de datos

drop database mydb;

3) Crear base de datos

create database mydb;

4) Esquema de importación

psql mydb < schema.sql

Sandip Ransing
fuente
9

En este caso, probablemente sería mejor tener una base de datos vacía que use como plantilla y cuando necesite actualizar, descarte la base de datos existente y cree una nueva a partir de la plantilla.

Scott Bailey
fuente
3

Puedes hacer esto con bash también:

#!/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 "{}"

Deberá ajustar los nombres de esquema, las contraseñas y los nombres de usuario para que coincidan con sus esquemas.

simao
fuente
3

AUTO_INCREMENTVersión de limpieza :

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
fuente
3

Chicos, la forma mejor y limpia es:

1) Crear volcado de esquema de base de datos (--schema-only) pg_dump mydb -s> schema.sql

2) Eliminar la base de datos eliminar la base de datos mydb;

3) Crear base de datos crear base de datos mydb;

4) Importar esquema psql mydb <schema.sql

¡Es trabajo para mí!

Que tengas un buen día. Hiram Walker

Hiram Walker
fuente
2

Si puede usar psql , puede usar el \gexeccomando meta para ejecutar el resultado de la 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 

Tenga en cuenta que \gexecse introduce en la versión 9.6

Sahap Asci
fuente
1

Para eliminar los datos y preservar las estructuras de tabla en pgAdmin , puede hacer:

  • Haga clic con el botón derecho en la base de datos -> copia de seguridad, seleccione "Solo esquema"
  • Suelta la base de datos
  • Crea una nueva base de datos y nómbrala como la anterior
  • Haga clic derecho en la nueva base de datos -> restaurar -> seleccione la copia de seguridad, seleccione "Solo esquema"
MYNDSTREAm
fuente