使用SQL完全复制Postgres表


76

免责声明:此问题与此处的堆栈溢出问题相似,但是这些答案都无法解决我的问题,我将在后面解释。

我正在尝试在postgres中复制一个大表(〜4000万行,100多个列),在其中索引了许多列。目前,我使用以下SQL:

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

此方法有两个问题:

  1. 它在数据摄取之前添加索引,因此比创建没有索引的表要花费更多的时间,然后在复制所有数据后再建立索引。
  2. 这不会正确复制“ SERIAL”样式列。它没有在新表上设置新的“计数器”,而是将新表中列的默认值设置为过去表的计数器,这意味着它不会随着行的添加而增加。

表的大小使索引成为实时问题。这也使得转储到文件然后重新注册变得不可行。我也没有命令行的优势。我需要在SQL中执行此操作。

我想做的就是要么用一些奇迹命令直接复制一个精确的副本,要么(如果不可能)复制带有所有约束但没有索引的表,并确保它们是“精神上的”约束(又名SERIAL列的新计数器)。然后使用a复制所有数据,SELECT *然后复制所有索引。

资料来源

  1. 有关数据库复制的堆栈溢出问题:这不是我要问的三个原因

    • 它使用命令行选项pg_dump -t x2 | sed 's/x2/x3/g' | psql,在此设置下,我无权访问命令行
    • 它在数据摄取之前创建索引,这很慢
    • 它不能正确更新序列列作为证据 default nextval('x1_id_seq'::regclass)
  2. 重置postgres表的序列值的方法:很好,但是很不幸,它非常手工。



我看到了这个问题,没有令人满意的答案可以真正满足我的要求,但这促使我对帖子进行另一次编辑。
Erik

该页面上投票最多的解决方案存在三个主要问题。第一,他们使用pg_dump -t x2 | sed 's/x2/x3/g' | psql我也无权访问的命令行功能。第二,它会在添加数据之前创建索引,这将非常慢!三,SERIAL的默认参数仍引用第一个表。default nextval('x1_id_seq'::regclass).这是我在问题中已经指出的三个缺陷。您是在告诉我这些方法没有解决方案吗?@peter
Erik

这只是编程的一小部分。
Peter Eisentraut 2011年

Answers:


66

好吧,不幸的是,您将不得不手工做一些这样的事情。但这一切都可以通过psql之类的方法完成。第一个命令很简单:

select * into newtable from oldtable

这将使用旧表的数据创建新表,但不使用索引。然后,您必须自己创建索引和序列等。您可以使用以下命令获取表上所有索引的列表:

select indexdef from pg_indexes where tablename='oldtable';

然后运行psql -E访问您的数据库,并使用\ d查看旧表。然后,您可以修改这两个查询以获取有关序列的信息:

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;

将上面的74359替换为从上一个查询中获得的oid。


请注意,如果您随后希望序列依赖于新的父表,则必须使用“由newtable.column拥有的更改序列seqname;”。
Scott Marlowe

谢谢。通过对该代码进行一些修改,我可以执行所需的操作。
艾瑞克(Erik)

73

create table asPostgreSQL中的功能现在可能是OP所寻找的答案。

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

create table my_table_copy as
  select * from my_table

这将创建与数据相同的表。

添加with no data将复制不包含数据的模式。

create table my_table_copy as
  select * from my_table
with no data

这将创建具有所有数据的表,但没有索引和触发器等。


create table my_table_copy (like my_table including all)

像语法这样的create table将包括所有触发器,索引,约束等。但不包括数据。


5
我很久以前就发布了这个问题,以至于我没有简便的方法可以轻松地对此进行验证。但是,“创建表为”似乎并不复制与该表关联的其他对象,例如索引和序列。
埃里克

啊,你说得对@Erik。多可惜。如果有人发现它有帮助,我会留下我的答案。但是添加了一条注释,说它不会复制其他信息。谢谢。
2016年

1
确实,它对指出局限性非常有帮助。现在我们知道使用此方法时要注意的事项。谢谢!
frostymarvelous

(like my_table including all)可能无法满足不希望使用索引的OP的需求,但这对我尝试获取具有所有约束的表是完美的。
本体

16

最接近的“奇迹命令”类似于

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

特别是,这会在加载表数据之后负责创建索引。

但这并不会重置序列;您将必须自己编写脚本。


8

要完全复制表,包括表结构和数据,请使用以下语句:

CREATE TABLE new_table AS 
TABLE existing_table;

要复制不包含数据的表结构,可将WITH NO DATA子句添加到CREATE TABLE语句中,如下所示:

CREATE TABLE new_table AS 
TABLE existing_table 
WITH NO DATA;

要从现有表复制包含部分数据的表,请使用以下语句:

CREATE TABLE new_table AS 
SELECT
*
FROM
    existing_table
WHERE
    condition;

您可以链接到参考吗?我在postgresql.org上找不到有关此语法的任何信息。一些值得注意的问题是:a)保留索引吗?b)这个有效的postgres版本是什么?
Erik

1
CREATE TABLE new_table AS TABLE existing_table没有数据;这不会复制表的整个结构(如索引,触发器,约束等)。
拉朱·艾哈迈德

2

警告:

所有使用pg_dump和任何形式的正则表达式替换源表名称的答案都是非常危险的。如果您的数据包含要替换的子字符串怎么办?您最终将更改数据!

我提出了两种解决方案:

  1. 使用一些特定于数据的正则表达式从转储中消除数据行
  2. 在其余行上执行搜索和替换

这是用Ruby编写的示例:

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

在上面,我试图将“成员”表复制到“ members_copy_20130320”中。我的数据正则表达式为/^\d+\t.*(?:t|f)$/

上述类型的解决方案对我有效。买者自负...

编辑:

好的,这是不喜欢regexp的人的伪shell语法的另一种方式:

  1. pg_dump -s -t mytable mydb> mytable_schema.sql
  2. 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. 在数据部分前面的几条SQL语句中替换“ mytable”
  6. psql -f mytable_data.sql mydb

0

显然,您想“重建”表。如果只想重建表而不复制表,则应改用CLUSTER。

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

您可以选择索引,然后尝试选择一个适合您的查询的索引。如果没有其他合适的索引,则可以始终使用主键。

如果您的表太大而无法缓存,则CLUSTER可能会变慢。


我实际上确实想复制,我删除了与问题无关的额外代码。据我所知,CLUSTER只是根据索引对行进行重新排序,这并不是我真正想要的。很抱歉提供错误信息。
Erik

-1

创建表newTableName(如包含索引的oldTableName);插入到newTableName中,选择* from oldTableName

这对我有用9.3

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.