Memotong semua tabel dalam database Postgres

155

Saya secara teratur perlu menghapus semua data dari database PostgreSQL saya sebelum membangun kembali. Bagaimana saya melakukan ini secara langsung dalam SQL?

Saat ini saya sudah berhasil membuat pernyataan SQL yang mengembalikan semua perintah yang perlu saya jalankan:

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

Tapi saya tidak bisa melihat cara untuk menjalankannya secara terprogram begitu saya memilikinya.

Sig
sumber

Jawaban:

226

FrustratedWithFormsDesigner sudah benar, PL / pgSQL dapat melakukan ini. Ini skripnya:

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;

Ini menciptakan fungsi tersimpan (Anda perlu melakukan ini sekali saja) yang kemudian dapat Anda gunakan seperti ini:

SELECT truncate_tables('MYUSER');
Henning
sumber
1
Harus rejig sedikit tetapi setelah itu bekerja seperti pesona! Saya tidak pernah menggunakan plpgsql sebelumnya jadi ini akan membuat saya berumur. Terima kasih! Bagi siapa pun yang membutuhkannya, saya telah menambahkan kode yang akhirnya saya gunakan di bagian bawah posting ini.
Sig
Maaf, saya mungkin berpikir dalam Oracle PL / SQL :( Saya memperbaiki kesalahan sintaks dalam kode saya di atas.
Henning
1
Anda juga dapat memindahkan pernyataan SELECT langsung ke loop FOR. DECLARE r RECORD;kemudian untuk loop: FOR r IN SELECT tablename FROM pg_tables LOOP
Michael Buen
6
Saya akan menambahkan CASCADE ke TRUNCATE TABLE
Bogdan Gusiev
3
OH TUHAN!! Saya baru saja memotong semua tabel saya di skema "publik" .... tolong tambahkan parameter lain dari "skema" sehingga fungsi memotong tabel hanya pada skema yang disediakan!
roneo
95

Kursor eksplisit jarang dibutuhkan dalam plpgsql. Gunakan kursor implisit yang lebih sederhana dan lebih cepat dari sebuah FORloop:

Catatan: Karena nama tabel tidak unik untuk setiap basis data, Anda harus memastikan skema-kualifikasi nama tabel untuk memastikan. Juga, saya membatasi fungsi ke skema 'publik' default. Sesuaikan dengan kebutuhan Anda, tetapi pastikan untuk mengecualikan skema sistem pg_*dan information_schema.

Berhati -hatilah dengan fungsi-fungsi ini. Mereka mengabaikan database Anda. Saya menambahkan perangkat keamanan anak. Komentari RAISE NOTICEgaris dan batalkan komentar EXECUTEuntuk meledakkan bom ...

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()membutuhkan Postgres 9.1 atau lebih baru. Di versi yang lebih lama, gabungkan string kueri seperti ini:

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

Perintah tunggal, tanpa loop

Karena kita dapat TRUNCATEbeberapa tabel sekaligus, kita tidak memerlukan kursor atau loop sama sekali:

Gabungkan semua nama tabel dan jalankan satu pernyataan. Lebih sederhana, lebih cepat:

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;

Panggilan:

SELECT truncate_tables('postgres');

Kueri yang disempurnakan

Anda bahkan tidak memerlukan fungsi. Di Postgres 9.0+ Anda dapat menjalankan perintah dinamis dalam sebuah DOpernyataan. Dan dalam Postgres 9.5+ sintaksanya bisa lebih sederhana:

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$;

Tentang perbedaan antara pg_class, pg_tablesdan information_schema.tables:

Tentang regclassdan mengutip nama tabel:

Untuk penggunaan berulang

Buat database "templat" (sebut saja my_template) dengan struktur vanilla Anda dan semua tabel kosong. Kemudian, lewati a DROP/CREATE DATABASE cycle:

DROP DATABASE mydb;
CREATE DATABASE mydb TEMPLATE my_template;

Ini sangat cepat , karena Postgres menyalin seluruh struktur pada level file. Tidak ada masalah konkurensi atau overhead lainnya yang memperlambat Anda.

Jika koneksi bersamaan membuat Anda tidak menjatuhkan DB, pertimbangkan:

Erwin Brandstetter
sumber
1
Perlu dicatat bahwa fungsi terakhir ini menghapus SEMUA basis data. Bukan hanya yang terhubung saat ini .... yeah ... panggil saya naif tapi itu benar-benar tidak jelas dari posting ini.
Amalgovinus
@Amalgovinus: Fungsi terakhir apa? Tidak ada fungsi di jawaban saya menyentuh apa pun di luar database saat ini (kecuali DROP DATABASE mydb, jelas). Apakah Anda membingungkan skema dengan database, mungkin?
Erwin Brandstetter
3
@Amalgovinus: Tidak, itu tidak mungkin. The DOperintah (seperti pernyataan SQL lainnya) dijalankan dalam database saat ini secara eksklusif . Postgres tidak memiliki cara untuk mengakses database lain dalam transaksi yang sama. Anda harus menggunakan dblink atau FDW untuk melakukan itu. Tapi itu tidak mempengaruhi semua skema dalam database saat ini - kecuali jika Anda menambahkan WHERE t.schemaname = 'public'untuk membatasi efek ke salah satu skema tertentu dalam kasus ini.
Erwin Brandstetter
1
Sangat menyenangkan untuk mengetahui tentang template tersebut. Ini dapat saya manfaatkan bahkan dalam skenario pengujian otomatis, di mana pengaturan ulang / persiapan basis data mungkin diperlukan.
hbobenicio
3
Terima kasih atas jawaban yang bagus, saya menggunakan "Perintah tunggal, tanpa loop" yang mengembalikan perintah TRUNCATE, bagaimana saya harus menjalankannya?
Mahyar
40

Jika saya harus melakukan ini, saya hanya akan membuat skema sql dari db saat ini, kemudian drop & buat db, kemudian muat db dengan skema sql.

Berikut adalah langkah-langkahnya:

1) Buat skema dump database ( --schema-only)

pg_dump mydb -s > schema.sql

2) Jatuhkan basis data

drop database mydb;

3) Buat Basis Data

create database mydb;

4) Skema Impor

psql mydb < schema.sql

Sandip Ransing
sumber
9

Dalam hal ini mungkin akan lebih baik untuk hanya memiliki database kosong yang Anda gunakan sebagai templat dan ketika Anda perlu menyegarkan, jatuhkan basis data yang ada dan buat yang baru dari templat.

Scott Bailey
sumber
3

Anda dapat melakukannya dengan bash juga:

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

Anda perlu menyesuaikan nama skema, kata sandi, dan nama pengguna untuk mencocokkan skema Anda.

simao
sumber
3

AUTO_INCREMENTVersi pembersihan :

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

Guys cara yang lebih baik dan bersih adalah dengan:

1) Membuat skema dump database (-schema-only) pg_dump mydb -s> schema.sql

2) Drop database drop database mydb;

3) Buat Database buat database mydb;

4) Impor Skema psql mydb <schema.sql

Ini bekerja untuk saya!

Semoga harimu menyenangkan. Hiram Walker

Hiram Walker
sumber
2

Jika Anda bisa menggunakan psql Anda bisa menggunakan \gexecperintah meta untuk mengeksekusi output query;

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 

Catatan yang \gexecdiperkenalkan ke versi 9.6

Sahap Asci
sumber
1

Untuk menghapus data dan mempertahankan struktur tabel di pgAdmin, Anda dapat melakukannya:

  • Klik kanan database -> cadangan, pilih "Hanya skema"
  • Jatuhkan basis data
  • Buat database baru dan beri nama seperti yang sebelumnya
  • Klik kanan database baru -> restore -> pilih cadangan, pilih "Skema saja"
mYnDstrEAm
sumber