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:
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
Stack Overflow question sur la copie de base de données : Ce n'est pas ce que je demande pour trois raisons
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.default nextval('x1_id_seq'::regclass)
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.
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.
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.
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;
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:
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:
psql -f mytable_copy_schema.sql mydb
pg_dump -a -t mytable mydb> mytable_data.sql
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.