如何在 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”的字段,但是我现在希望添加一个约束以确保 yearuser_idsidcid 都是唯一的,但我不能应用约束,因为已经存在违反此约束的重复值。

有没有办法找到所有重复项?

【问题讨论】:

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&lt;B.id 替换为A.ctid&lt;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 如何查找并删除重复数据

如何在 PostgreSQL 中查找重复记录

在 MongoDB 中查找重复记录

在 PostgreSQL 12 上使用 WITH 查找唯一值、计算重复项并对其进行排名

如何在postgresql的列中针对单个记录选择多个值[重复]

sql 如何使用PostgreSQL查找重复值