【发布时间】:2011-09-28 20:39:39
【问题描述】:
我在 PostgreSQL 8.3.8 数据库中有一个表,它上面没有键/约束,并且有多行具有完全相同的值。
我想删除所有重复项并只保留每行的 1 个副本。
有一个特别的列(名为“key”)可用于识别重复项,即每个不同的“key”应该只存在一个条目。
我该怎么做? (理想情况下,使用单个 SQL 命令。)
在这种情况下,速度不是问题(只有几行)。
【问题讨论】:
标签: sql postgresql duplicates
我在 PostgreSQL 8.3.8 数据库中有一个表,它上面没有键/约束,并且有多行具有完全相同的值。
我想删除所有重复项并只保留每行的 1 个副本。
有一个特别的列(名为“key”)可用于识别重复项,即每个不同的“key”应该只存在一个条目。
我该怎么做? (理想情况下,使用单个 SQL 命令。)
在这种情况下,速度不是问题(只有几行)。
【问题讨论】:
标签: sql postgresql duplicates
更快的解决方案是
DELETE FROM dups a USING (
SELECT MIN(ctid) as ctid, key
FROM dups
GROUP BY key HAVING COUNT(*) > 1
) b
WHERE a.key = b.key
AND a.ctid <> b.ctid
【讨论】:
ctid?
DELETE FROM dupes a
WHERE a.ctid <> (SELECT min(b.ctid)
FROM dupes b
WHERE a.key = b.key);
【讨论】:
这既快速又简洁:
DELETE FROM dupes T1
USING dupes T2
WHERE T1.ctid < T2.ctid -- delete the older versions
AND T1.key = T2.key; -- add more columns if needed
另请参阅我在 How to delete duplicate rows without unique identifier 的回答,其中包含更多信息。
【讨论】:
ctid 指向表中记录的物理位置。与我当时在评论中写的相反,使用小于运算符不一定指向旧版本,因为 ct 可以环绕,并且具有较低 ctid 的值实际上可能更新。
min(ctid) 时保留了旧记录吗?而你的保留较新的?谢谢!
我试过了:
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);
由 Postgres wiki 提供:
【讨论】:
id,则此列将不起作用。
id 其中 column1...3 重复
EXISTS 很简单,对于大多数数据分布来说是最快的:
DELETE FROM dupes d
WHERE EXISTS (
SELECT FROM dupes
WHERE key = d.key
AND ctid < d.ctid
);
从每组重复行(由相同的key 定义)中,这会保留ctid 最少的一行。
结果与currently accepted answer by a_horse 相同。只是更快,因为EXISTS 可以在找到第一个违规行后立即停止评估,而min() 的替代方案必须考虑所有 em> 每组的行数来计算最小值。 这个问题与速度无关,但为什么不考虑呢?
您可能希望在清理后添加UNIQUE constraint,以防止重复出现:
ALTER TABLE dupes ADD CONSTRAINT constraint_name_here UNIQUE (key);
关于系统专栏ctid:
如果在表中定义了任何其他列UNIQUE NOT NULL 列(如PRIMARY KEY),那么请务必使用它而不是ctid。
如果key 可以是NULL,而您也只需要其中一个,请使用IS NOT DISTINCT FROM 而不是=。见:
因为这样比较慢,所以您可以按原样运行上述查询,并且另外:
DELETE FROM dupes d
WHERE key IS NULL
AND EXISTS (
SELECT FROM dupes
WHERE key IS NULL
AND ctid < d.ctid
);
并考虑:
对于小型表,索引通常对性能没有帮助。我们不需要再看下去了。
对于大表和少数个重复,(key) 上的现有索引可以提供帮助(很多)。
对于大部分重复,索引可能会增加成本而不是收益,因为它必须同时保持最新。无论如何,查找没有索引的重复项会变得更快,因为有这么多,EXISTS 只需要找到一个。但是,如果您负担得起(即允许并发访问),请考虑一种完全不同的方法:将少数幸存的行写入新表。这也消除了过程中的表(和索引)膨胀。见:
【讨论】:
我会使用临时表:
create table tab_temp as
select distinct f1, f2, f3, fn
from tab;
然后,删除tab 并将tab_temp 重命名为tab。
【讨论】:
DROP TABLE IF EXISTS tmp; CREATE TABLE tmp as ( SELECT * from (SELECT DISTINCT * FROM your_table) as t ); DELETE from your_table; INSERT INTO your_table SELECT * from tmp; DROP TABLE tmp;
我必须创建自己的版本。 @a_horse_with_no_name 编写的版本在我的表上太慢了(21M 行)。而且@rapimo 根本不会删除重复。
这是我在 PostgreSQL 9.5 上使用的
DELETE FROM your_table
WHERE ctid IN (
SELECT unnest(array_remove(all_ctids, actid))
FROM (
SELECT
min(b.ctid) AS actid,
array_agg(ctid) AS all_ctids
FROM your_table b
GROUP BY key1, key2, key3, key4
HAVING count(*) > 1) c);
【讨论】:
另一种方法(仅当您的表中有任何唯一字段(如 id)时才有效)按列查找所有唯一 ID 并删除不在唯一列表中的其他 ID
DELETE
FROM users
WHERE users.id NOT IN (SELECT DISTINCT ON (username, email) id FROM users);
【讨论】:
怎么样:
和 u AS (SELECT DISTINCT * FROM your_table), x AS(从 your_table 中删除) INSERT INTO your_table SELECT * FROM u;我一直担心执行顺序,DELETE 是否会在 SELECT DISTINCT 之前发生,但它对我来说很好。 并且不需要任何关于表结构的知识。
【讨论】:
json),这将不起作用。
这是using PARTITION BY 和virtual ctid column 的解决方案,它的作用类似于主键,至少在单个会话中:
DELETE FROM dups
USING (
SELECT
ctid,
(
ctid != min(ctid) OVER (PARTITION BY key_column1, key_column2 [...])
) AS is_duplicate
FROM dups
) dups_find_duplicates
WHERE dups.ctid == dups_find_duplicates.ctid
AND dups_find_duplicates.is_duplicate
子查询用于将所有行标记为重复行,这取决于它们是否共享相同的“键列”,但与行的“分区”中的“第一个”行是否共享相同的ctid共享相同的密钥。
换句话说,“第一”被定义为:
min(ctid) OVER (PARTITION BY key_column1, key_column2 [...])然后,is_duplicate 为 true 的所有行都被它们的ctid 删除。
来自文档,ctid represents(强调我的):
行版本在其表中的物理位置。请注意,虽然 ctid 可用于非常快速地定位行版本,但如果行的 ctid 被 VACUUM FULL 更新或移动,它会发生变化。因此 ctid 作为长期行标识符是无用的。应该使用主键来标识逻辑行。
【讨论】:
Postgresql 有 windows 功能,你可以使用 rank() 来归档你的目标,示例:
WITH ranked as (
SELECT
id, column1,
"rank" () OVER (
PARTITION BY column1
order by column1 asc
) AS r
FROM
table1
)
delete from table1 t1
using ranked
where t1.id = ranked.id and ranked.r > 1
【讨论】:
这对我来说效果很好。我有一个包含重复值的表,术语。运行查询以使用所有重复行填充临时表。然后我在临时表中使用这些 id 运行删除语句。 value 是包含重复项的列。
CREATE TEMP TABLE dupids AS
select id from (
select value, id, row_number()
over (partition by value order by value)
as rownum from terms
) tmp
where rownum >= 2;
delete from [table] where id in (select id from dupids)
【讨论】: