Answers:
例如,您可以:
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
。:)
其中一些方法似乎有些复杂,我通常这样做是:
给定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。USING
postgresql中的功能吗?
WHERE table1.ctid<table2.ctid
-无需添加串行列
除了创建新表外,您还可以在截断后将唯一的行重新插入到同一表中。做这一切在一个事务中。(可选)您可以使用来将临时表自动放在事务末尾ON COMMIT DROP
。见下文。
只有在要从表中删除许多行的情况下,此方法才有用。对于一些重复项,请使用Plain DELETE
。
您提到了数百万行。为了加快操作速度,您需要为会话分配足够的临时缓冲区。必须在当前会话中使用任何临时缓冲区之前调整该设置。找出表的大小:
SELECT pg_size_pretty(pg_relation_size('tbl'));
进行相应设置temp_buffers
。由于内存中的表示需要更多的RAM,因此可以进行大量舍入。
SET temp_buffers = 200MB; -- example value
BEGIN;
-- CREATE TEMPORARY TABLE t_tmp ON COMMIT DROP AS -- drop temp table at commit
CREATE TEMPORARY TABLE t_tmp AS -- retain temp table after commit
SELECT DISTINCT * FROM tbl; -- DISTINCT folds duplicates
TRUNCATE tbl;
INSERT INTO tbl
SELECT * FROM t_tmp;
-- ORDER BY id; -- optionally "cluster" data while being at it.
COMMIT;
如果存在依赖的对象,则此方法可能优于创建新表。视图,索引,外键或其他引用该表的对象。TRUNCATE
让你用干净的石板开始呢(在后台新的文件),并且是多快于DELETE FROM tbl
大表(DELETE
其实是可以用小桌子更快)。
对于大表,删除索引和外键,重新填充表并重新创建这些对象通常更快。至于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中的非常大的表,创建新表的速度将大大提高。您必须权衡此问题与依赖对象可能带来的麻烦/开销。
TRUNCATE
。正如Erwin所说,在删除表之前,请确保它存在。见@ codebykat的答案
ON COMMIT DROP
,这样错过了我在“一次交易中”写过部分的人们就不会丢失数据。并且我添加了BEGIN / COMMIT来阐明“一项交易”。
您可以使用oid或ctid,它们通常是表中的“不可见”列:
DELETE 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)
-或使用其他任何列或一组列进行排序以选择幸存者。
NOT EXISTS
吗?
EXISTS
这里。像这样阅读:“删除存在其他任何行的所有行,并以相同的值添加dist_col
一个更大的行ctid
”。每组骗子中唯一的幸存者将是最大的幸存者ctid
。
LIMIT
如果您知道重复的数量,可以与之配合使用。
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);
请参阅删除重复项。
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
);
通用查询删除重复项:
DELETE FROM table_name
WHERE ctid NOT IN (
SELECT max(ctid) FROM table_name
GROUP BY column1, [column 2, ...]
);
该列ctid
是可用于每个表的特殊列,但除非特别说明,否则不可见。该ctid
列的值被认为是表中的每一行都是唯一的。
GROUP BY
子句是必不可少的-这应该是现在违反的“唯一性标准”,或者如果您想要检测重复项的键。如果指定错误,它将无法正常工作
我只是成功地使用了Erwin Brandstetter的答案来删除联接表(缺少自己的主ID的表)中的重复项,但发现有一个重要警告。
包括ON COMMIT DROP
意味着临时表将在事务结束时被删除。对我来说,这意味着我要插入临时表时该临时表不再可用!
我只是做了CREATE TEMPORARY TABLE t_tmp AS SELECT DISTINCT * FROM tbl;
,一切都很好。
临时表确实在会话结束时被删除。
此函数删除重复项而不删除索引,并将其复制到任何表。
用法: select remove_duplicates('mytable');
--- -remove_duplicates(tablename)从表中删除重复记录(从集合转换为唯一集合) --- 创建或替换功能remove_duplicates(text)返回无效的$$ 宣布 表名ALIAS FOR $ 1; 开始 执行“创建临时表_DISTINCT_” || 表名|| 'AS(SELECT DISTINCT * FROM'||表名||');'; 执行'DELETE FROM'|| 表名|| ';'; 执行'INSERT INTO'|| 表名|| '(SELECT * FROM _DISTINCT_'||表名||');'; 执行'DROP TABLE _DISTINCT_'|| 表名|| ';'; 返回; 结束; $$ LANGUAGE plpgsql;
如果您只有一个或几个重复的条目,并且确实是重复的(即,它们出现了两次),则可以使用ctid
上面建议的“隐藏” 列,以及LIMIT
:
DELETE FROM mytable WHERE ctid=(SELECT ctid FROM mytable WHERE […] LIMIT 1);
这将仅删除所选行的第一行。
首先,您需要确定要保留哪些“重复项”。如果所有列都相等,那么可以删除其中的任何一个。。。但是也许您只想保留最新列或其他条件?
最快的方法取决于您对上述问题的回答,还取决于表格中重复项的百分比。如果丢弃50%的行,则最好这样做CREATE TABLE ... AS SELECT DISTINCT ... FROM ... ;
,如果删除1%的行,则使用DELETE更好。
同样对于这样的维护操作,通常最好将work_mem
RAM 设置为一个很大的块:运行EXPLAIN,检查排序/哈希数N,然后将work_mem设置为RAM / 2 / N。对速度有好处。只要您只有一个并发连接...
我正在使用PostgreSQL 8.4。当我运行建议的代码时,我发现它实际上并没有删除重复项。在运行一些测试时,我发现添加“ DISTINCT ON(duplicate_column_name)”和“ ORDER BYplicate_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;
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)
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);