Tronquer toutes les tables d'une base de données Postgres

155

J'ai régulièrement besoin de supprimer toutes les données de ma base de données PostgreSQL avant une reconstruction. Comment faire cela directement dans SQL?

Pour le moment, j'ai réussi à créer une instruction SQL qui renvoie toutes les commandes dont j'ai besoin pour exécuter:

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

Mais je ne vois pas un moyen de les exécuter par programme une fois que je les ai.

Sig
la source

Réponses:

226

FrustratedWithFormsDesigner est correct, PL / pgSQL peut le faire. Voici le 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;

Cela crée une fonction stockée (vous devez le faire juste une fois) que vous pouvez ensuite utiliser comme ceci:

SELECT truncate_tables('MYUSER');
Henning
la source
1
Il a fallu revoir un peu mais après ça a fonctionné comme un charme! Je n'ai jamais utilisé plpgsql auparavant, donc cela m'aurait pris des années. Merci! Pour tous ceux qui en ont besoin, j'ai ajouté le code que j'ai fini par utiliser au bas de cet article.
Sig
Désolé, je pensais probablement à Oracle PL / SQL: (J'ai corrigé l'erreur de syntaxe dans mon code ci-dessus.
Henning
1
vous pouvez également déplacer l'instruction SELECT directement vers la boucle FOR. DECLARE r RECORD;puis pour boucle: FOR r IN SELECT tablename FROM pg_tables LOOP
Michael Buen
6
J'ajouterais CASCADE à TRUNCATE TABLE
Bogdan Gusiev
3
OMG!! Je viens de tronquer toutes mes tables dans le schéma "public" .... les pls ajoutent un autre paramètre de "schéma" afin que la fonction tronque les tables uniquement sur le schéma qui est fourni!
roneo
95

Les curseurs explicites sont rarement nécessaires dans plpgsql. Utilisez le curseur implicite plus simple et plus rapide d'une FORboucle:

Remarque: étant donné que les noms de table ne sont pas uniques par base de données, vous devez les qualifier de schéma pour être sûr. De plus, je limite la fonction au schéma par défaut «public». Adaptez-vous à vos besoins, mais veillez à exclure les schémas système pg_*et information_schema.

Soyez très prudent avec ces fonctions. Ils détruisent votre base de données. J'ai ajouté un dispositif de sécurité pour enfants. Commentez la RAISE NOTICEligne et décommentez EXECUTEpour amorcer la bombe ...

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()nécessite Postgres 9.1 ou version ultérieure. Dans les anciennes versions, concaténez la chaîne de requête comme ceci:

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

Commande unique, pas de boucle

Puisque nous pouvons TRUNCATEplusieurs tables à la fois, nous n'avons pas du tout besoin de curseur ou de boucle:

Regroupez tous les noms de table et exécutez une seule instruction. Plus simple, plus rapide:

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;

Appel:

SELECT truncate_tables('postgres');

Requête affinée

Vous n'avez même pas besoin d'une fonction. Dans Postgres 9.0+, vous pouvez exécuter des commandes dynamiques dans une DOinstruction. Et dans Postgres 9.5+, la syntaxe peut être encore plus 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$;

À propos de la différence entre pg_class, pg_tableset information_schema.tables:

À propos regclasset noms de table cités:

Pour usage répété

Créez une base de données "modèle" (nommons-la my_template) avec votre structure vanille et toutes les tables vides. Puis passez par un cycle DROP/CREATE DATABASE :

DROP DATABASE mydb;
CREATE DATABASE mydb TEMPLATE my_template;

C'est extrêmement rapide , car Postgres copie toute la structure au niveau du fichier. Aucun problème de concurrence ou autre surcharge ne vous ralentit.

Si les connexions simultanées vous empêchent de supprimer la base de données, envisagez:

Erwin Brandstetter
la source
1
Il convient de noter que cette dernière fonction a effacé TOUTES les bases de données. Pas seulement celui actuellement connecté ... ouais ... appelez-moi naiive, mais ce n'était vraiment pas clair dans ce post.
Amalgovinus
@Amalgovinus: Quelle dernière fonction? Aucune des fonctions de ma réponse ne touche quoi que ce soit en dehors de la base de données actuelle (sauf DROP DATABASE mydb, évidemment). Vous confondez peut-être les schémas avec les bases de données?
Erwin Brandstetter
3
@Amalgovinus: Non, c'est impossible. La DOcommande (comme toute autre instruction SQL) est exécutée exclusivement dans la base de données courante . Postgres n'a aucun moyen d'accéder à d'autres bases de données dans la même transaction. Vous devrez utiliser dblink ou FDW pour ce faire. Mais il n'affecte tous les schémas de la base de données en cours - sauf si vous ajoutez à limiter l'effet à un schéma particulier dans ce cas particulier. WHERE t.schemaname = 'public'
Erwin Brandstetter
1
Vraiment agréable de connaître ces modèles. Cela peut être utile même dans les scénarios de tests automatisés, où une réinitialisation / préparation de la base de données peut être nécessaire.
hbobenicio
3
Merci pour cette excellente réponse, j'utilise "Commande unique, pas de boucle" qui renvoie la commande TRUNCATE, comment dois-je l'exécuter?
Mahyar
40

Si je dois faire cela, je vais simplement créer un schéma sql de la base de données actuelle, puis déposer et créer la base de données, puis charger la base de données avec le schéma sql.

Voici les étapes impliquées:

1) Créer un vidage de schéma de la base de données ( --schema-only)

pg_dump mydb -s > schema.sql

2) Déposer la base de données

drop database mydb;

3) Créer une base de données

create database mydb;

4) Schéma d'importation

psql mydb < schema.sql

Sandip Ransing
la source
9

Dans ce cas, il serait probablement préférable d'avoir simplement une base de données vide que vous utilisez comme modèle et lorsque vous devez actualiser, supprimez la base de données existante et créez-en une nouvelle à partir du modèle.

Scott Bailey
la source
3

Vous pouvez également le faire avec bash:

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

Vous devrez ajuster les noms de schéma, les mots de passe et les noms d'utilisateur pour qu'ils correspondent à vos schémas.

Simao
la source
3

AUTO_INCREMENTVersion de nettoyage :

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
la source
3

Les gars, la meilleure façon est de:

1) Créer un vidage de schéma de la base de données (--schema-only) pg_dump mydb -s> schema.sql

2) Drop database drop database mydb;

3) Créer une base de données créer une base de données mydb;

4) Importer le schéma psql mydb <schema.sql

Ça marche pour moi!

Bonne journée. Hiram Walker

Hiram Walker
la source
2

Si vous pouvez utiliser psql, vous pouvez utiliser la \gexeccommande meta pour exécuter la sortie de la requête;

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 

Notez qu'il \gexecest introduit dans la version 9.6

Sahap Asci
la source
1

Pour supprimer les données et conserver les structures de table dans pgAdmin, vous pouvez faire:

  • Faites un clic droit sur la base de données -> sauvegarde, sélectionnez "Schéma uniquement"
  • Supprimer la base de données
  • Créez une nouvelle base de données et nommez-la comme l'ancienne
  • Cliquez-droit sur la nouvelle base de données -> restaurer -> sélectionnez la sauvegarde, sélectionnez "Schéma uniquement"
mYnDstrEAm
la source