我必须向现有表添加唯一约束。这很好,只是表已经有数百万行,并且许多行违反了我需要添加的唯一约束。
删除违规行的最快方法是什么?我有一个 SQL 语句,它可以找到重复项并删除它们,但它需要永远运行。有没有其他方法可以解决这个问题?也许备份表,然后在添加约束后恢复?
我必须向现有表添加唯一约束。这很好,只是表已经有数百万行,并且许多行违反了我需要添加的唯一约束。
删除违规行的最快方法是什么?我有一个 SQL 语句,它可以找到重复项并删除它们,但它需要永远运行。有没有其他方法可以解决这个问题?也许备份表,然后在添加约束后恢复?
其中一些方法似乎有点复杂,我通常这样做:
给定 table 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。例如,您可以:
CREATE TABLE tmp ...
INSERT INTO tmp SELECT DISTINCT * FROM t;
DROP TABLE t;
ALTER TABLE tmp RENAME TO t;
除了创建新表之外,您还可以在截断后将唯一行重新插入同一个表中。一次性完成所有操作。
这种方法仅在需要从整个表中删除大量行的情况下才有用。对于几个重复项,请使用普通的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
无论如何,让您从一个干净的状态开始(后台的新文件)并且比使用大表要快得多(实际上可以使用小表更快)。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
那里更快。但对于小桌子来说可能更快(更简单!)。
如果您根本没有依赖对象,您可能会创建一个新表并删除旧表,但是通过这种通用方法您几乎没有任何收获。
对于不适合可用 RAM的非常大的表,创建新表会快得多。您必须权衡依赖对象可能带来的麻烦/开销。
您可以使用 oid 或 ctid,它们通常是表中的“不可见”列:
DELETE FROM table
WHERE ctid NOT IN
(SELECT MAX(s.ctid)
FROM table s
GROUP BY s.column_has_be_distinct);
PostgreSQL 窗口函数很方便解决这个问题。
DELETE 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
);
此函数在不删除索引的情况下删除重复项,并对任何表执行此操作。
用法:select remove_duplicates('mytable');
--- --- remove_duplicates(tablename) 从表中删除重复记录(从集合转换为唯一集合) --- 创建或替换函数 remove_duplicates(text) RETURNS void AS $$ 宣布 表名别名为 1 美元; 开始 执行'创建临时表_DISTINCT_' || 表名 || ' AS (SELECT DISTINCT * FROM ' || 表名 || ');'; 执行'删除'|| 表名 || ';'; 执行“插入”|| 表名 || ' (SELECT * FROM _DISTINCT_' || 表名 || ');'; 执行“删除表_DISTINCT_”|| 表名 || ';'; 返回; 结尾; $$ 语言 plpgsql;
我刚刚成功地使用Erwin Brandstetter 的回答来删除连接表(缺少自己的主 ID 的表)中的重复项,但发现有一个重要的警告。
包括ON COMMIT DROP
意味着临时表将在事务结束时被删除。对我来说,这意味着在我插入临时表时它不再可用!
我刚刚做了CREATE TEMPORARY TABLE t_tmp AS SELECT DISTINCT * FROM tbl;
,一切正常。
临时表确实在会话结束时被删除。
首先,您需要决定要保留哪些“重复”。如果所有列都相等,好的,您可以删除其中任何一个...但也许您只想保留最新的或其他一些标准?
最快的方法取决于您对上述问题的回答,以及表中重复的百分比。如果你丢弃 50% 的行,你最好这样做CREATE TABLE ... AS SELECT DISTINCT ... FROM ... ;
,如果你删除 1% 的行,使用 DELETE 会更好。
同样对于像这样的维护操作,通常最好设置work_mem
一大块 RAM:运行 EXPLAIN,检查排序/哈希的数量 N,并将 work_mem 设置为 RAM / 2 / N。使用大量 RAM;这对速度有好处。只要您只有一个并发连接...
DELETE FROM table
WHERE something NOT IN
(SELECT MAX(s.something)
FROM table As s
GROUP BY s.this_thing, s.that_thing);
如果您只有一个或几个重复条目,并且它们确实重复(即它们出现两次),您可以使用ctid
上面建议的“隐藏”列,以及LIMIT
:
DELETE FROM mytable WHERE ctid=(SELECT ctid FROM mytable WHERE […] LIMIT 1);
这将仅删除所选行中的第一行。
CREATE TABLE test (col text);
INSERT INTO test VALUES
('1'),
('2'), ('2'),
('3'),
('4'), ('4'),
('5'),
('6'), ('6');
DELETE FROM test
WHERE ctid in (
SELECT t.ctid FROM (
SELECT row_number() over (
partition BY col
ORDER BY col
) AS rnum,
ctid FROM test
ORDER BY col
) t
WHERE t.rnum >1);
我正在使用 PostgreSQL 8.4。当我运行建议的代码时,我发现它实际上并没有删除重复项。在运行一些测试时,我发现添加“DISTINCT ON (duplicate_column_name)”和“ORDER BY duplicate_column_name”可以解决问题。我不是 SQL 专家,我在 PostgreSQL 8.4 SELECT...DISTINCT 文档中找到了这个。
CREATE OR REPLACE FUNCTION remove_duplicates(text, text) RETURNS void AS $$
DECLARE
tablename ALIAS FOR $1;
duplicate_column ALIAS FOR $2;
BEGIN
EXECUTE 'CREATE TEMPORARY TABLE _DISTINCT_' || tablename || ' AS (SELECT DISTINCT ON (' || duplicate_column || ') * FROM ' || tablename || ' ORDER BY ' || duplicate_column || ' ASC);';
EXECUTE 'DELETE FROM ' || tablename || ';';
EXECUTE 'INSERT INTO ' || tablename || ' (SELECT * FROM _DISTINCT_' || tablename || ');';
EXECUTE 'DROP TABLE _DISTINCT_' || tablename || ';';
RETURN;
END;
$$ LANGUAGE plpgsql;
这非常好用并且非常快:
CREATE INDEX otherTable_idx ON otherTable( colName );
CREATE TABLE newTable AS select DISTINCT ON (colName) col1,colName,col2 FROM otherTable;
DELETE 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);
按列删除重复项并保留具有最低 id 的行。该模式取自postgres wiki
使用 CTE,您可以通过此实现上述内容的更具可读性的版本
WITH duplicate_ids as (
SELECT id, rnum
FROM num_of_rows
WHERE rnum > 1
),
num_of_rows as (
SELECT id,
ROW_NUMBER() over (partition BY column1,
column2,
column3 ORDER BY id) AS rnum
FROM tablename
)
DELETE FROM tablename
WHERE id IN (SELECT id from duplicate_ids)