我需要在现有的表中添加唯一约束。但这个表已经有数百万行数据,并且其中许多行违反了我需要添加的唯一约束。
如何最快速地删除这些问题行?我有一个SQL语句可以查找并删除重复行,但运行时间非常长。是否有其他解决方法?例如在添加约束后备份表,然后恢复数据?
我需要在现有的表中添加唯一约束。但这个表已经有数百万行数据,并且其中许多行违反了我需要添加的唯一约束。
如何最快速地删除这些问题行?我有一个SQL语句可以查找并删除重复行,但运行时间非常长。是否有其他解决方法?例如在添加约束后备份表,然后恢复数据?
这些方法中有一些看起来有点复杂,我通常会按照以下方式进行操作:
给定表格 table
,想要在 (field1, field2) 上去重并保留 field3 最大的行:
DELETE FROM table USING table alias
WHERE table.field1 = alias.field1 AND table.field2 = alias.field2 AND
table.max_field < alias.max_field
例如,我有一个名为user_accounts
的表,我想在电子邮件上添加唯一约束,但是有一些重复项。假设我想保留最近创建的一个(在重复项中具有最大的id值)。DELETE FROM user_accounts USING user_accounts ua2
WHERE user_accounts.email = ua2.email AND user_account.id < ua2.id;
USING
不是标准的 SQL,它是 PostgreSQL 的一个扩展(但非常有用),但原问题明确提到了 PostgreSQL。USING
不是标准的 SQL,它是 PostgreSQL 的一个扩展(但非常有用),但原问题明确提到了 PostgreSQL。)CREATE TABLE tmp ...
INSERT INTO tmp SELECT DISTINCT * FROM t;
DROP TABLE t;
ALTER TABLE tmp RENAME TO t;
CREATE TABLE tmp AS SELECT ...;
,这样您甚至不需要弄清楚tmp
的布局。 :) - Randal Schwartz不需要创建新表,您也可以在将其截断后重新插入唯一行到同一张表中。在一个事务中完成所有操作。
这种方法仅适用于需要从整个表中删除大量行的情况。对于只有少量重复项的情况,请使用普通的DELETE
。
您提到了数百万行。为了使操作更加快速,您需要为会话分配足够的临时缓冲区。该设置必须在当前会话中的任何临时缓冲区被使用之前进行调整。找出您的表的大小:
SELECT pg_size_pretty(pg_relation_size('tbl'));
将temp_buffers
设置为比这个值稍高一点。
SET temp_buffers = 200MB; -- example value
BEGIN;
CREATE TEMP TABLE t_tmp AS -- retains temp for duration of session
SELECT DISTINCT * FROM tbl -- DISTINCT folds duplicates
ORDER BY id; -- optionally "cluster" data
TRUNCATE tbl;
INSERT INTO tbl
SELECT * FROM t_tmp; -- retains order (implementation detail)
COMMIT;
TRUNCATE
无论如何都会让您从干净的板 slate 开始(在后台创建新文件),并且比使用 DELETE FROM tbl
快得多(对于大型表格,DELETE
实际上可能更快)。对于大型表格,删除索引和外键(FK),重新填充表格并重新创建这些对象通常更快。至于 FK 约束,当然,您必须确保新数据有效,否则在尝试创建 FK 时会遇到异常。请注意,TRUNCATE
需要比 DELETE
更积极地锁定。这可能是具有重度并发负载的表格的问题。但它仍然比完全删除和替换表格不那么破坏性。TRUNCATE
或者对于小到中等大小的表格,可以使用类似技巧,使用数据修改CTE(Postgres 9.1+):WITH del AS (DELETE FROM tbl RETURNING *)
INSERT INTO tbl
SELECT DISTINCT * FROM del;
ORDER BY id; -- optionally "cluster" data while being at it.
对于大表而言,由于TRUNCATE
速度更快,因此速度会变慢。但是对于小表而言,可能更快(也更简单)。
如果您完全没有依赖对象,您可以创建一个新表并删除旧表,但是与这种通用方法相比,您几乎不会获得任何好处。
对于无法适应可用内存的非常大的表格,创建一个新表将更快。您需要权衡这一点,以免出现依赖对象的麻烦/开销。
TRUNCATE
后临时表不存在。正如 Erwin 所说,一定要确保在截断表之前它存在。请参考 @codebykat 的答案。 - Jordan ArsenaultON COMMIT DROP
的版本,这样那些错过我写的“在一个事务中”的人就不会丢失数据了。并且我添加了 BEGIN / COMMIT 来澄清“一个事务”。 - Erwin BrandstetterDELETE
是否更适合? - Fopa Léon ConstantinDELETE FROM table
WHERE ctid NOT IN
(SELECT MAX(s.ctid)
FROM table s
GROUP BY s.column_has_be_distinct);
NOT EXISTS
应该更快:DELETE FROM tbl t WHERE EXISTS (SELECT 1 FROM tbl t1 WHERE t1.dist_col = t.dist_col AND t1.ctid > t.ctid)
-- 或使用任何其他列或一组列进行排序以选择幸存者。 - Erwin BrandstetterNOT EXISTS
? - JohnEXISTS
。理解为:“删除所有存在具有相同值但ctid
更大的dist_col
行的任何其他行”。每个重复组中唯一幸存者将是具有最大ctid
的那个。 - Erwin BrandstetterDELETE FROM tablename
WHERE id IN (SELECT id
FROM (SELECT id,
row_number() over (partition BY column1, column2, column3 ORDER BY id) AS rnum
FROM tablename) t
WHERE t.rnum > 1);
通用查询以删除重复项:
DELETE FROM table_name
WHERE ctid NOT IN (
SELECT max(ctid) FROM table_name
GROUP BY column1, [column 2, ...]
);
ctid
列是每个表都有的特殊列,但除非特别提到,否则不可见。在表中,ctid
列值被认为是每一行唯一的。请参阅PostgreSQL系统列以了解更多关于ctid
的信息。
create table test ( a text, b text );
insert into test values ( 'x', 'y');
insert into test values ( 'x', 'x');
insert into test values ( 'y', 'y' );
insert into test values ( 'y', 'x' );
insert into test values ( 'x', 'y');
insert into test values ( 'x', 'x');
insert into test values ( 'y', 'y' );
insert into test values ( 'y', 'x' );
insert into test values ( 'x', 'y');
select oid, a, b from test;
select o.oid, o.a, o.b from test o
where exists ( select 'x'
from test i
where i.a = o.a
and i.b = o.b
and i.oid < o.oid
);
注意:PostgreSQL不支持在from
子句中提到的表上使用别名进行删除。
delete from test
where exists ( select 'x'
from test i
where i.a = test.a
and i.b = test.b
and i.oid < test.oid
);
ON COMMIT DROP
意味着临时表将在事务结束时被删除。对我来说,这意味着在我插入它之前,临时表已经不再可用!CREATE TEMPORARY TABLE t_tmp AS SELECT DISTINCT * FROM tbl;
,一切都运行良好。这个函数可以在不删除索引的情况下,从任何表中去除重复记录。
用法: select remove_duplicates('mytable');
--- --- remove_duplicates(tablename) 从表中删除重复记录(从集合转换为唯一集合) --- CREATE OR REPLACE FUNCTION remove_duplicates(text) RETURNS void AS $$ DECLARE tablename ALIAS FOR $1; BEGIN EXECUTE 'CREATE TEMPORARY TABLE _DISTINCT_' || tablename || ' AS (SELECT DISTINCT * FROM ' || tablename || ');'; EXECUTE 'DELETE FROM ' || tablename || ';'; EXECUTE 'INSERT INTO ' || tablename || ' (SELECT * FROM _DISTINCT_' || tablename || ');'; EXECUTE 'DROP TABLE _DISTINCT_' || tablename || ';'; RETURN; END; $$ LANGUAGE plpgsql;
DELETE FROM table
WHERE something NOT IN
(SELECT MAX(s.something)
FROM table As s
GROUP BY s.this_thing, s.that_thing);
USING
是做什么的吗? - Fopa Léon ConstantinWHERE table1.ctid<table2.ctid
- 无需添加序列列。 - alexkovelsky