Vollständiges Kopieren einer Postgres-Tabelle mit SQL

73

HAFTUNGSAUSSCHLUSS: Diese Frage ähnelt der Frage zum Stapelüberlauf hier , aber keine dieser Antworten funktioniert für mein Problem, wie ich später erläutern werde.

Ich versuche, eine große Tabelle (~ 40 Millionen Zeilen, mehr als 100 Spalten) in Postgres zu kopieren, in der viele Spalten indiziert sind. Derzeit verwende ich dieses SQL-Bit:

CREATE TABLE <tablename>_copy (LIKE <tablename> INCLUDING ALL);
INSERT INTO <tablename>_copy SELECT * FROM <tablename>;

Diese Methode hat zwei Probleme:

  1. Es fügt die Indizes vor der Datenaufnahme hinzu, sodass es viel länger dauert, als die Tabelle ohne Indizes zu erstellen und nach dem Kopieren aller Daten zu indizieren.
  2. Dadurch werden Spalten im SERIAL-Stil nicht ordnungsgemäß kopiert. Anstatt einen neuen 'Zähler' für die neue Tabelle einzurichten, wird der Standardwert der Spalte in der neuen Tabelle auf den Zähler der letzten Tabelle gesetzt, was bedeutet, dass er beim Hinzufügen von Zeilen nicht erhöht wird.

Die Tabellengröße macht die Indizierung zu einem Echtzeitproblem. Es macht es auch unmöglich, in eine Datei zu kopieren, um sie dann erneut aufzunehmen. Ich habe auch nicht den Vorteil einer Kommandozeile. Ich muss dies in SQL tun.

Was ich tun möchte, ist entweder direkt eine exakte Kopie mit einem Wunderbefehl zu erstellen oder, wenn dies nicht möglich ist, die Tabelle mit allen Einschränkungen, aber ohne Indizes, zu kopieren und sicherzustellen, dass es sich um die Einschränkungen "im Geiste" handelt (auch bekannt als) einen neuen Zähler für eine SERIAL-Spalte). Kopieren Sie dann alle Daten mit a SELECT *und kopieren Sie dann alle Indizes.

Quellen

  1. Frage zum Stapelüberlauf beim Kopieren von Datenbanken : Dies ist aus drei Gründen nicht das, wonach ich frage

    • Es verwendet die Befehlszeilenoption pg_dump -t x2 | sed 's/x2/x3/g' | psqlund in dieser Einstellung habe ich keinen Zugriff auf die Befehlszeile
    • Es werden die Indizes vor der Datenaufnahme erstellt, was langsam ist
    • Es aktualisiert die seriellen Spalten nicht korrekt als Beweis von default nextval('x1_id_seq'::regclass)
  2. Methode zum Zurücksetzen des Sequenzwerts für eine Postgres-Tabelle : Das ist großartig, aber leider sehr manuell.

Erik
quelle
2
Ihre Frage ist wahrscheinlich ein Duplikat von stackoverflow.com/questions/198141/…
goodside
Ich habe diese Frage gesehen, es gab keine zufriedenstellenden Antworten, die tatsächlich das tun können, was ich anfordere, aber dies veranlasst mich, meinen Beitrag erneut zu bearbeiten.
Erik
Es gibt drei Hauptprobleme mit der am besten bewerteten Lösung auf dieser Seite. Erstens verwenden sie Befehlszeilenfunktionen, auf pg_dump -t x2 | sed 's/x2/x3/g' | psqldie ich auch keinen Zugriff habe. Zweitens werden die Indizes erstellt, bevor die Daten hinzugefügt werden, die sehr langsam sind! Drittens verweist der Standardparameter der SERIAL immer noch auf die erste Tabelle. default nextval('x1_id_seq'::regclass).Dies sind drei Fehler, auf die ich bereits in meiner Frage hingewiesen habe. Sie sagen mir, dass es für keines dieser Probleme eine Lösung gibt? @ Peter
Erik
Es ist nur eine kleine Frage der Programmierung.
Peter Eisentraut

Antworten:

63

Nun, du musst leider einige dieser Sachen von Hand machen. Aber es kann alles von so etwas wie psql gemacht werden. Der erste Befehl ist einfach genug:

select * into newtable from oldtable

Dadurch wird eine Newtable mit den Daten von Oldtable erstellt, jedoch nicht mit Indizes. Dann müssen Sie die Indizes und Sequenzen usw. selbst erstellen. Mit dem folgenden Befehl können Sie eine Liste aller Indizes für eine Tabelle abrufen:

select indexdef from pg_indexes where tablename='oldtable';

Führen Sie dann psql -E aus, um auf Ihre Datenbank zuzugreifen, und verwenden Sie \ d, um die alte Tabelle anzuzeigen. Sie können diese beiden Abfragen dann entstellen, um Informationen zu den Sequenzen zu erhalten:

SELECT c.oid,
  n.nspname,
  c.relname
FROM pg_catalog.pg_class c
     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname ~ '^(oldtable)$'
  AND pg_catalog.pg_table_is_visible(c.oid)
ORDER BY 2, 3;

SELECT a.attname,
  pg_catalog.format_type(a.atttypid, a.atttypmod),
  (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid) for 128)
   FROM pg_catalog.pg_attrdef d
   WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef),
  a.attnotnull, a.attnum
FROM pg_catalog.pg_attribute a
WHERE a.attrelid = '74359' AND a.attnum > 0 AND NOT a.attisdropped
ORDER BY a.attnum;

Ersetzen Sie die obige 74359 durch die OID, die Sie aus der vorherigen Abfrage erhalten haben.

Scott Marlowe
quelle
Beachten Sie, dass Sie, wenn Sie möchten, dass die Sequenzen von der neuen übergeordneten Tabelle abhängig sind, "alter sequence seqname von newtable.column" verwenden müssen.
Scott Marlowe
Vielen Dank. Mit einigen Änderungen dieses Codes kann ich tun, was ich brauchte.
Erik
68

Die create table asFunktion in PostgreSQL könnte nun die Antwort sein, nach der das OP gesucht hat.

https://www.postgresql.org/docs/9.5/static/sql-createtableas.html

create table my_table_copy as
  select * from my_table

Dadurch wird eine identische Tabelle mit den Daten erstellt.

Durch Hinzufügen with no datawird das Schema ohne die Daten kopiert.

create table my_table_copy as
  select * from my_table
with no data

Dadurch wird die Tabelle mit allen Daten erstellt, jedoch ohne Indizes und Trigger usw.


create table my_table_copy (like my_table including all)

Die Syntax zum Erstellen einer Tabelle enthält alle Trigger, Indizes, Einschränkungen usw., jedoch keine Daten.

Phill
quelle
4
Ich habe diese Frage vor so langer Zeit gestellt, dass ich keine einfache Möglichkeit habe, dies einfach zu überprüfen. Es scheint jedoch nicht so, als würde "Tabelle erstellen als" andere mit der Tabelle verknüpfte Objekte wie Indizes und Sequenzen kopieren.
Erik
Ah du hast recht @Erik. Schade. Ich werde meine Antwort hinterlassen, falls jemand anderes sie hilfreich findet. Es wurde jedoch eine Notiz hinzugefügt, die besagt, dass die anderen Informationen nicht kopiert werden. Vielen Dank.
Phill
1
In der Tat war es sehr hilfreich, um die Einschränkungen aufzuzeigen. Jetzt wissen wir, worauf wir bei dieser Methode achten müssen. Vielen Dank!
frostymarvelous
(like my_table including all)Möglicherweise befriedigt das OP nicht, das keine Indizes wollte, aber es ist perfekt für mich, wenn ich versuche, eine Tabelle mit allen Einschränkungen zu erhalten.
Noumenon
16

Der nächste "Wunderbefehl" ist so etwas wie

pg_dump -t tablename | sed -r 's/\btablename\b/tablename_copy/' | psql -f -

Dies sorgt insbesondere dafür, dass die Indizes nach dem Laden der Tabellendaten erstellt werden.

Aber das setzt die Sequenzen nicht zurück; Sie müssen das selbst schreiben.

Peter Eisentraut
quelle
8

Verwenden Sie die folgende Anweisung, um eine Tabelle einschließlich Tabellenstruktur und Daten vollständig zu kopieren:

CREATE TABLE new_table AS 
TABLE existing_table;

Um eine Tabellenstruktur ohne Daten zu kopieren, fügen Sie der Anweisung CREATE TABLE die WITH NO DATA-Klausel wie folgt hinzu:

CREATE TABLE new_table AS 
TABLE existing_table 
WITH NO DATA;

Um eine Tabelle mit Teildaten aus einer vorhandenen Tabelle zu kopieren, verwenden Sie die folgende Anweisung:

CREATE TABLE new_table AS 
SELECT
*
FROM
    existing_table
WHERE
    condition;
KM Rakibul Islam
quelle
Können Sie auf eine Referenz verlinken? Ich konnte auf postgresql.org keine Informationen zu dieser Syntax finden. Einige bemerkenswerte Fragen sind: a) Werden die Indizes beibehalten? b) In welchen Versionen von Postgres ist dies gültig?
Erik
1
CREATE TABLE new_table AS TABLE existierende_table OHNE DATEN; Dadurch wird nicht die gesamte Struktur (wie Index, Trigger, Einschränkungen usw.) einer Tabelle kopiert.
Raju Ahmed
2

WARNUNG:

Alle Antworten, die pg_dump und jede Art von regulärem Ausdruck verwenden, um den Namen der Quelltabelle zu ersetzen, sind wirklich gefährlich. Was ist, wenn Ihre Daten den Teilstring enthalten, den Sie ersetzen möchten? Sie werden am Ende Ihre Daten ändern!

Ich schlage eine Zwei-Pass-Lösung vor:

  1. Entfernen Sie Datenleitungen aus dem Speicherauszug, indem Sie einen datenspezifischen regulären Ausdruck verwenden
  2. Führen Sie das Suchen und Ersetzen in den verbleibenden Zeilen durch

Hier ist ein Beispiel in Ruby:

ruby -pe 'gsub(/(members?)/, "\\1_copy_20130320") unless $_ =~ /^\d+\t.*(?:t|f)$/' < members-production-20130320.sql > copy_members_table-20130320.sql

Oben versuche ich, die Tabelle "Mitglieder" in "Mitglieder_kopie_20130320" zu kopieren. Mein datenspezifischer regulärer Ausdruck ist /^\d+\t.*(?:t|f)$/

Die obige Art der Lösung funktioniert für mich. Vorbehalt Emptor ...

bearbeiten:

OK, hier ist eine andere Möglichkeit in der Pseudo-Shell-Syntax für die regexp-abgeneigten Personen:

  1. pg_dump -s -t mytable mydb> mytable_schema.sql
  2. Suchen und Ersetzen des Tabellennamens in mytable_schema.sql> mytable_copy_schema.sql
  3. psql -f mytable_copy_schema.sql mydb

  4. pg_dump -a -t mytable mydb> mytable_data.sql

  5. Ersetzen Sie "mytable" in den wenigen SQL-Anweisungen vor dem Datenabschnitt
  6. psql -f mytable_data.sql mydb
Tomek
quelle
0

Anscheinend möchten Sie eine Tabelle "neu erstellen". Wenn Sie eine Tabelle nur neu erstellen und nicht kopieren möchten, sollten Sie stattdessen CLUSTER verwenden.

SELECT count(*) FROM table; -- make a seq scan to make sure the table is at least
                            -- decently cached
CLUSTER someindex ON table;

Sie können den Index auswählen und versuchen, einen auszuwählen, der Ihren Abfragen entspricht. Sie können den Primärschlüssel immer verwenden, wenn kein anderer Index geeignet ist.

Wenn Ihre Tabelle zu groß ist, um zwischengespeichert zu werden, kann CLUSTER jedoch langsam sein.

Bobflux
quelle
Ich möchte tatsächlich kopieren, ich habe den zusätzlichen Code entfernt, der für die Frage nicht wirklich relevant war. Nach allem, was ich sagen kann, ordnet CLUSTER die Zeilen nur basierend auf dem Index neu an, was nicht wirklich das ist, wonach ich suche. Entschuldigung für die Fehlinformationen.
Erik
-1

Tabelle erstellen newTableName (wie oldTableName einschließlich Indizes); In newTableName einfügen Wählen Sie * aus oldTableName

Das hat bei mir funktioniert 9.3

user2940756
quelle