【问题标题】:How to find duplicate records in PostgreSQL如何在 PostgreSQL 中查找重复记录
【发布时间】:2015-03-25 06:14:22
【问题描述】:

我有一个名为“user_links”的 PostgreSQL 数据库表,它目前允许以下重复字段:

year, user_id, sid, cid

唯一约束目前是第一个名为“id”的字段,但是我现在希望添加一个约束以确保 yearuser_idsidcid 都是唯一的,但我不能应用约束,因为已经存在违反此约束的重复值。

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

【问题讨论】:

标签: sql postgresql duplicates


【解决方案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】:

    在您的情况下,由于限制,您需要删除重复的记录。

    1. 查找重复行
    2. created_at 日期组织它们 - 在这种情况下,我保留最旧的
    3. 删除带有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 没有问题,但之后就不需要了)

    【讨论】:

    • 对于我所面临的情况,这是最有效、最准确的解决方案
    猜你喜欢
    • 2021-05-17
    • 1970-01-01
    • 2019-08-06
    • 1970-01-01
    • 2023-01-24
    • 2020-02-07
    • 2015-02-14
    • 2010-10-25
    • 2015-01-15
    相关资源
    最近更新 更多