如何在 PostgreSQL 中查找重复记录
Posted
技术标签:
【中文标题】如何在 PostgreSQL 中查找重复记录【英文标题】:How to find duplicate records in PostgreSQL 【发布时间】:2015-03-25 06:14:22 【问题描述】:我有一个名为“user_links”的 PostgreSQL 数据库表,它目前允许以下重复字段:
year, user_id, sid, cid
唯一约束目前是第一个名为“id”的字段,但是我现在希望添加一个约束以确保 year
、user_id
、sid
和 cid
都是唯一的,但我不能应用约束,因为已经存在违反此约束的重复值。
有没有办法找到所有重复项?
【问题讨论】:
Find duplicate rows with PostgreSQL 的可能重复项 【参考方案1】:基本思想是使用带有计数聚合的嵌套查询:
select * from yourTable ou
where (select count(*) from yourTable inr
where inr.sid = ou.sid) > 1
您可以调整内部查询中的 where 子句以缩小搜索范围。
对于 cmets 中提到的问题,还有另一个很好的解决方案,(但不是每个人都阅读它们):
select Column1, Column2, count(*)
from yourTable
group by Column1, Column2
HAVING count(*) > 1
或更短:
SELECT (yourTable.*)::text, count(*)
FROM yourTable
GROUP BY yourTable.*
HAVING count(*) > 1
【讨论】:
你也可以使用 HAVING:select co1, col2, count(*) from tbl group by col1, col2 HAVING count(*)>1
感谢@alexkovelsky,拥有语句对我来说更容易修改并且运行得更快。我会建议一个答案,以获得更高的知名度。
这些选项对我有用,其他选项对结果进行分组,这些选项为我提供了所有重复的记录,而不仅仅是重复的记录,谢谢!
我的这个答案有点慢。在 10k 行 * 18 列的表上,查询耗时 8 秒
那就是那里的果酱,兄弟。哎呀。谢谢。 ?【参考方案2】:
来自“Find duplicate rows with PostgreSQL”的智能解决方案如下:
select * from (
SELECT id,
ROW_NUMBER() OVER(PARTITION BY column1, column2 ORDER BY id asc) AS Row
FROM tbl
) dups
where
dups.Row > 1
【讨论】:
这很快!在几分之一秒内处理了数百万行。其他答案只是挂在那里...... 据我所知,此查询不考虑组内的所有行。它只显示某些东西的重复项,部分重复项的 rownum = 1。如果我错了,请纠正我 @vladimir Filipchenko 要在所有行中使用它,请在 Alexkovelsky 解决方案中添加一个级别:SELECT * FROM ( SELECT *, LEAD(row,1) OVER () AS nextrow FROM ( SELECT *, ROW_NUMBER() OVER(w) AS row FROM tbl WINDOW w AS (PARTITION BY col1, col2 ORDER BY col3) ) x ) y WHERE row > 1 OR nextrow > 1;
@VladimirFilipchenko 只需将ROW_NUMBER()
替换为COUNT(*)
,并在ORDER BY id asc
后添加rows between unbounded preceding and unbounded following
比我找到的其他解决方案要好得多。也同样适用于删除带有DELETE ...USING
和一些小调整的欺骗【参考方案3】:
为了更简单,我假设您希望仅对列 year 应用唯一约束,并且主键是名为 id 的列。
为了找到你应该运行的重复值,
SELECT year, COUNT(id)
FROM YOUR_TABLE
GROUP BY year
HAVING COUNT(id) > 1
ORDER BY COUNT(id);
使用上面的 sql 语句,您会得到一个包含表中所有重复年份的表。为了删除除了最新的重复条目之外的所有重复项,您应该使用上面的 sql 语句。
DELETE
FROM YOUR_TABLE A USING YOUR_TABLE_AGAIN B
WHERE A.year=B.year AND A.id<B.id;
【讨论】:
简单有效。通过将A.id<B.id
替换为A.ctid<B.ctid
,可以在没有唯一列的静态表上使用
如果你在寻找count(*) = 1,这个组是必要的吗?
这应该是正确的答案【参考方案4】:
您可以在将被复制的字段上加入同一个表,然后在 id 字段上反加入。从第一个表别名 (tn1) 中选择 id 字段,然后对第二个表别名的 id 字段使用 array_agg 函数。最后,为了使 array_agg 函数正常工作,您将按 tn1.id 字段对结果进行分组。这将生成一个结果集,其中包含记录的 id 和符合连接条件的所有 id 的数组。
select tn1.id,
array_agg(tn2.id) as duplicate_entries,
from table_name tn1 join table_name tn2 on
tn1.year = tn2.year
and tn1.sid = tn2.sid
and tn1.user_id = tn2.user_id
and tn1.cid = tn2.cid
and tn1.id <> tn2.id
group by tn1.id;
显然,将在duplicate_entries 数组中对应一个id 的id 在结果集中也有自己的条目。您将不得不使用此结果集来决定您希望哪个 id 成为“真相”的来源。不应该被删除的一条记录。也许你可以这样做:
with dupe_set as (
select tn1.id,
array_agg(tn2.id) as duplicate_entries,
from table_name tn1 join table_name tn2 on
tn1.year = tn2.year
and tn1.sid = tn2.sid
and tn1.user_id = tn2.user_id
and tn1.cid = tn2.cid
and tn1.id <> tn2.id
group by tn1.id
order by tn1.id asc)
select ds.id from dupe_set ds where not exists
(select de from unnest(ds.duplicate_entries) as de where de < ds.id)
选择具有重复项的最小编号 ID(假设 ID 增加 int PK)。这些将是您要保留的 ID。
【讨论】:
【参考方案5】:在您的情况下,由于限制,您需要删除重复的记录。
-
查找重复行
按
created_at
日期组织它们 - 在这种情况下,我保留最旧的
删除带有USING
的记录以过滤正确的行
WITH duplicated AS (
SELECT id,
count(*)
FROM products
GROUP BY id
HAVING count(*) > 1),
ordered AS (
SELECT p.id,
created_at,
rank() OVER (partition BY p.id ORDER BY p.created_at) AS rnk
FROM products o
JOIN duplicated d ON d.id = p.id ),
products_to_delete AS (
SELECT id,
created_at
FROM ordered
WHERE rnk = 2
)
DELETE
FROM products
USING products_to_delete
WHERE products.id = products_to_delete.id
AND products.created_at = products_to_delete.created_at;
【讨论】:
“p.id”或“p.created_at”中的“p”是什么?最后一个 FROM 子句应该是“FROM products p”吗?【参考方案6】:受 Sandro Wiggers 的启发,我做了类似的事情
WITH ordered AS (
SELECT id,year, user_id, sid, cid,
rank() OVER (PARTITION BY year, user_id, sid, cid ORDER BY id) AS rnk
FROM user_links
),
to_delete AS (
SELECT id
FROM ordered
WHERE rnk > 1
)
DELETE
FROM user_links
USING to_delete
WHERE user_link.id = to_delete.id;
如果你想测试它,稍微改变一下:
WITH ordered AS (
SELECT id,year, user_id, sid, cid,
rank() OVER (PARTITION BY year, user_id, sid, cid ORDER BY id) AS rnk
FROM user_links
),
to_delete AS (
SELECT id,year,user_id,sid, cid
FROM ordered
WHERE rnk > 1
)
SELECT * FROM to_delete;
这将概述将要删除的内容(运行删除时在 to_delete 查询中保留 year,user_id,sid,cid 没有问题,但之后就不需要了)
【讨论】:
对于我所面临的情况,这是最有效、最准确的解决方案以上是关于如何在 PostgreSQL 中查找重复记录的主要内容,如果未能解决你的问题,请参考以下文章
在 PostgreSQL 12 上使用 WITH 查找唯一值、计算重复项并对其进行排名