web-dev-qa-db-fra.com

Copier complètement une table postgres avec SQL

DISCLAIMER: Cette question est similaire à la question de débordement de pile ici , mais aucune de ces réponses ne fonctionne pour mon problème, comme je l'expliquerai plus tard.

J'essaie de copier une grande table (environ 40 millions de lignes, plus de 100 colonnes) dans Postgres où de nombreuses colonnes sont indexées. Actuellement, j'utilise ce bit de SQL:

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

Cette méthode a deux problèmes:

  1. Il ajoute les index avant l’introduction des données. Il faudra donc beaucoup plus de temps que la création de la table sans index, puis l’indexation après la copie de toutes les données.
  2. Cela ne copie pas correctement les colonnes de style `SERIAL '. Au lieu de définir un nouveau «compteur» sur la nouvelle table, il définit la valeur par défaut de la colonne de la nouvelle table sur le compteur de la table passée, ce qui signifie qu’elle n’augmentera pas à mesure que des lignes sont ajoutées.

La taille de la table fait de l'indexation un problème en temps réel. Cela rend également impossible de vider un fichier pour ensuite le ré-ingérer. Je n'ai pas non plus l'avantage d'une ligne de commande. J'ai besoin de faire cela en SQL.

Ce que je voudrais faire est soit de faire une copie exacte avec une commande miracle, soit, si ce n’est pas possible, de copier le tableau avec toutes les contraintes mais sans index, et de s’assurer qu’elles sont les contraintes «en esprit» (aka un nouveau compteur pour une colonne SERIAL). Copiez ensuite toutes les données avec un SELECT *, puis copiez tous les index.

Sources

  1. Stack Overflow question sur la copie de base de données : Ce n'est pas ce que je demande pour trois raisons

    • Il utilise l'option pg_dump -t x2 | sed 's/x2/x3/g' | psql en ligne de commande et, dans ce paramètre, je n'ai pas accès à la ligne de commande.
    • Il crée la pré-indexation des données, ce qui est lent
    • Il ne met pas à jour correctement les colonnes de série comme preuve par default nextval('x1_id_seq'::regclass)
  2. Méthode pour réinitialiser la valeur de séquence d'une table postgres : C'est génial, mais malheureusement, il est très manuel.

54
Erik

Eh bien, vous allez devoir faire certaines choses à la main, malheureusement. Mais tout peut être fait à partir de quelque chose comme psql. La première commande est assez simple:

select * into newtable from oldtable

Cela créera newtable avec les données oldtable mais pas les index. Ensuite, vous devez créer vous-même les index, les séquences, etc. Vous pouvez obtenir une liste de tous les index d'une table avec la commande suivante:

select indexdef from pg_indexes where tablename='oldtable';

Puis exécutez psql -E pour accéder à votre base de données et utilisez\d pour consulter l’ancienne table. Vous pouvez ensuite modifier ces deux requêtes pour obtenir les informations sur les séquences:

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;

Remplacez le 74359 ci-dessus par l'oid que vous avez obtenu de la requête précédente.

52
Scott Marlowe

La "commande miracle" la plus proche est quelque chose comme

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

En particulier, cela prend en charge la création des index après le chargement des données de la table.

Mais cela ne réinitialise pas les séquences; vous devrez écrire cela vous-même.

14
Peter Eisentraut

Pour copier complètement une table, y compris la structure et les données, utilisez l'instruction suivante:

CREATE TABLE new_table AS 
TABLE existing_table;

Pour copier une structure de table sans données, vous ajoutez la clause WITH NO DATA à l'instruction CREATE TABLE comme suit:

CREATE TABLE new_table AS 
TABLE existing_table 
WITH NO DATA;

Pour copier une table avec des données partielles à partir d'une table existante, utilisez l'instruction suivante:

CREATE TABLE new_table AS 
SELECT
*
FROM
    existing_table
WHERE
    condition;
2
K M Rakibul Islam

ATTENTION:

Toutes les réponses qui utilisent pg_dump et toute sorte d’expression régulière pour remplacer le nom de la table source sont vraiment dangereuses. Que se passe-t-il si vos données contiennent la sous-chaîne que vous essayez de remplacer? Vous allez finir par changer vos données!

Je propose une solution en deux passes:

  1. éliminer les lignes de données du dump en utilisant certaines expressions rationnelles spécifiques aux données
  2. effectuer une recherche et un remplacement sur les lignes restantes

Voici un exemple écrit en Ruby:

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

Dans ce qui précède, j'essaie de copier la table "members" dans "members_copy_20130320". Mon expression rationnelle spécifique aux données est /^\d+\t.*(?:t|f)$/

Le type de solution ci-dessus fonctionne pour moi. Caveat emptor ...

modifier:

OK, voici une autre manière d'utiliser la syntaxe pseudo-shell pour les personnes avides de regexp:

  1. pg_dump -s -t mytable mydb> mytable_schema.sql
  2. nom de la table de recherche-remplacement dans 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. remplacez "mytable" dans les quelques instructions SQL précédant la section data
  6. psql -f mytable_data.sql mydb 
2
Tomek

Apparemment, vous voulez "reconstruire" une table. Si vous souhaitez uniquement reconstruire une table sans la copier, vous devez utiliser plutôt CLUSTER.

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

Vous devez choisir l’index, essayez d’en choisir un qui convient à vos requêtes. Vous pouvez toujours utiliser la clé primaire si aucun autre index ne convient.

Si votre table est trop grande pour être mise en cache, CLUSTER peut être lent.

0
peufeu