用 SQL 完全复制一个 postgres 表
Posted
技术标签:
【中文标题】用 SQL 完全复制一个 postgres 表【英文标题】:Completely copying a postgres table with SQL 【发布时间】:2011-09-29 22:14:56 【问题描述】:免责声明:这个问题类似于堆栈溢出问题here,但这些答案都不适用于我的问题,我稍后会解释。
我正在尝试在 postgres 中复制一个大表(约 40M 行,100 多列),其中很多列都已编入索引。目前我使用的是这段 SQL:
CREATE TABLE <tablename>_copy (LIKE <tablename> INCLUDING ALL);
INSERT INTO <tablename>_copy SELECT * FROM <tablename>;
这个方法有两个问题:
-
它在数据摄取之前添加索引,因此它比创建没有索引的表然后在复制所有数据后建立索引要花费更长的时间。
这不会正确复制“SERIAL”样式列。它没有在新表上设置新的“计数器”,而是将新表中列的默认值设置为过去表的计数器,这意味着它不会随着行的添加而增加。
表大小使索引成为一个实时问题。这也使得转储到文件然后重新摄取变得不可行。我也没有命令行的优势。我需要在 SQL 中执行此操作。
我想做的是直接使用一些神奇的命令制作一个精确的副本,或者如果这不可能,复制带有所有约束但没有索引的表,并确保它们是“精神上的约束” '(又名 SERIAL 列的新计数器)。然后使用SELECT *
复制所有数据,然后复制所有索引。
来源
Stack Overflow question about database copying: 这不是我要的三个原因
它使用命令行选项pg_dump -t x2 | sed 's/x2/x3/g' | psql
,在此设置中我无法访问命令行
它在数据摄取前创建索引,这很慢
它没有正确更新串行列作为default nextval('x1_id_seq'::regclass)
的证据
Method to reset the sequence value for a postgres table: 这个不错,可惜太手动了。
【问题讨论】:
您的问题可能与***.com/questions/198141/…重复 我看到了这个问题,没有令人满意的答案可以真正满足我的要求,但这促使我对我的帖子进行另一次编辑。 该页面上投票最多的解决方案存在三个主要问题。一,他们在pg_dump -t x2 | sed 's/x2/x3/g' | psql
中使用我也无权访问的命令行功能。二,它在添加数据之前创建索引,这将非常慢!三、SERIAL 的默认参数仍然引用第一个表default nextval('x1_id_seq'::regclass).
这是我在问题中已经指出的三个缺陷。你告诉我这些都没有解决办法? @彼得
这只是编程的小事。
【参考方案1】:
create table newTableName (like oldTableName including indexes);
insert into newTableName select * from oldTableName
这对我 9.3 有效
【讨论】:
【参考方案2】:要完整复制一个表,包括表结构和数据,请使用以下语句:
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 中有效? 创建表 new_table AS TABLE existing_table WITH NO DATA;这不会复制表的整个结构(如索引、触发器、约束等)。 sql table 命令 记录在postgresql.org/docs/13/sql-select.html 中select 命令 页面的末尾。table x
是 select * from x
的简写。 create table 和 create table as 是具有不同语法的不同文档页面。请参阅 postgresql.org/docs/13/sql-createtable.html 和 postgresql.org/docs/13/sql-createtableas.html。上面的查询只获取有/没有数据的列类型,但仍然很有用。【参考方案3】:
PostgreSQL 中的 create table as
功能现在可能是 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 as”似乎不会复制与该表关联的其他对象,例如索引和序列。 啊,你是对的@Erik。多可惜。我会留下我的答案,以防其他人觉得它有帮助。但是添加了一条注释说它不会复制其他信息。谢谢。 确实,它在指出限制方面非常有帮助。现在我们知道使用这种方法时要注意什么了。谢谢!(like my_table including all)
可能无法满足不想要索引的 OP,但它非常适合我尝试获取具有所有约束的表。【参考方案4】:
警告:
所有使用 pg_dump 和任何类型的正则表达式来替换源表名称的答案都是非常危险的。如果您的数据包含您要替换的子字符串怎么办?你最终会改变你的数据!
我提出了一个两遍的解决方案:
-
使用一些特定于数据的正则表达式从转储中删除数据行
对剩余的行执行搜索和替换
这是一个用 Ruby 编写的示例:
ruby -pe 'gsub(/(members?)/, "\\1_copy_20130320") unless $_ =~ /^\d+\t.*(?:t|f)$/' < members-production-20130320.sql > copy_members_table-20130320.sql
在上面我试图将“members”表复制到“members_copy_20130320”中。我的数据特定正则表达式是 /^\d+\t.*(?:t|f)$/
上述类型的解决方案对我有用。警告购买者...
编辑:
好的,对于不喜欢正则表达式的人来说,这是伪 shell 语法的另一种方式:
-
pg_dump -s -t mytable mydb > mytable_schema.sql
在 mytable_schema.sql > mytable_copy_schema.sql 中搜索并替换表名
psql -f mytable_copy_schema.sql mydb
pg_dump -a -t mytable mydb > mytable_data.sql
在数据部分前面的几个 SQL 语句中替换“mytable” psql -f mytable_data.sql mydb【讨论】:
【参考方案5】:好吧,不幸的是,您将不得不手工完成其中的一些工作。但这一切都可以通过 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。
【讨论】:
请注意,如果您希望序列依赖于新的父表,则必须使用“alter sequence seqnameowned by newtable.column;”【参考方案6】:最接近的“奇迹命令”类似于
pg_dump -t tablename | sed -r 's/\btablename\b/tablename_copy/' | psql -f -
特别是,这会在加载表数据后创建索引。
但这不会重置序列;你必须自己编写脚本。
【讨论】:
注意下面的重要警告(Tomek);如果您的数据恰好包含表名,则此命令将损坏您的数据!为了安全起见,我在 emacs 而不是 sed 中进行了手动搜索和替换。【参考方案7】:显然你想“重建”一个表。如果你只想重建一个表,而不是复制它,那么你应该使用 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 只是根据索引对行重新排序,这并不是我真正想要的。很抱歉提供错误信息。以上是关于用 SQL 完全复制一个 postgres 表的主要内容,如果未能解决你的问题,请参考以下文章
如何使用复制命令在 postgres 中将数据从一个表复制到另一个表