如何删除 PostgreSQL 连接表中的重复记录?

mar*_*ion 9 postgresql duplication

我有一个表,其架构如下:

create_table "questions_tags", :id => false, :force => true do |t|
        t.integer "question_id"
        t.integer "tag_id"
      end

      add_index "questions_tags", ["question_id"], :name => "index_questions_tags_on_question_id"
      add_index "questions_tags", ["tag_id"], :name => "index_questions_tags_on_tag_id"
Run Code Online (Sandbox Code Playgroud)

我想删除重复的记录,即它们既具有相同的记录tag_idquestion_id与另一条记录相同。

SQL 看起来像什么?

Erw*_*ter 15

根据我的经验(以及许多测试中所示),@gsiemsNOT IN证明的速度相当慢,而且扩展性极差。逆IN通常更快(您可以在这种情况下重新制定这种方式),但是这个查询EXISTS(完全按照您的要求进行)应该更快 - 使用数量级的大表:

DELETE FROM questions_tags q
WHERE  EXISTS (
   SELECT FROM questions_tags q1
   WHERE  q1.ctid < q.ctid
   AND    q1.question_id = q.question_id
   AND    q1.tag_id = q.tag_id
   );
Run Code Online (Sandbox Code Playgroud)

删除其中的每一行与同另一行(tag_id, question_id)和一个较小的ctid存在。(根据元组的物理顺序有效地保留第一个实例。)ctid在没有更好的替代方案的情况下使用,您的表似乎没有 PK 或任何其他唯一(一组)列。

ctid行中存在的内部元组标识符,并且必须是唯一的。进一步阅读:

测试

我运行了一个测试用例,该表与您的问题和 100k 行匹配:

CREATE TABLE questions_tags(
  question_id integer NOT NULL
, tag_id      integer NOT NULL
);

INSERT INTO questions_tags (question_id, tag_id)
SELECT (random()* 100)::int, (random()* 100)::int
FROM   generate_series(1, 100000);

ANALYZE questions_tags;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,索引没有帮助。

结果

NOT IN
SQLfiddle超时。
在本地尝试了相同的方法,但几分钟后我也取消了它。

EXISTS
在这个SQLfiddle 中完成半秒

备择方案

如果要删除大部分行,将幸存者选择到另一个表中,删除原始表并重命名幸存者表会更快。小心,如果您在原始文件上定义了视图或外键(或其他依赖项),这会产生影响。

如果您有依赖项并希望保留它们,您可以:

  • 删除所有外键和索引 - 为了性能。
  • SELECT 幸存者到一个临时表。
  • TRUNCATE 原本的。
  • 重新INSERT幸存者。
  • 重新CREATE索引和外键。视图可以保持不变,它们对性能没有影响。更多在这里在这里


gsi*_*ems 6

您可以使用 ctid 来完成此操作。例如:

创建一个重复的表:

=# create table foo (id1 integer, id2 integer);
CREATE TABLE

=# insert into foo values (1,1), (1, 2), (1, 2), (1, 3);
INSERT 0 4

=# select * from foo;
 id1 | id2 
-----+-----
   1 |   1
   1 |   2
   1 |   2
   1 |   3
(4 rows)
Run Code Online (Sandbox Code Playgroud)

选择重复数据:

=# select foo.ctid, foo.id1, foo.id2, foo2.min_ctid
-#  from foo
-#  join (
-#      select id1, id2, min(ctid) as min_ctid 
-#          from foo 
-#          group by id1, id2 
-#          having count (*) > 1
-#      ) foo2 
-#      on foo.id1 = foo2.id1 and foo.id2 = foo2.id2
-#  where foo.ctid <> foo2.min_ctid ;
 ctid  | id1 | id2 | min_ctid 
-------+-----+-----+----------
 (0,3) |   1 |   2 | (0,2)
(1 row)
Run Code Online (Sandbox Code Playgroud)

删除重复数据:

=# delete from foo
-# where ctid not in (select min (ctid) as min_ctid from foo group by id1, id2);
DELETE 1

=# select * from foo;
 id1 | id2 
-----+-----
   1 |   1
   1 |   2
   1 |   3
(3 rows)
Run Code Online (Sandbox Code Playgroud)

在您的情况下,以下内容应该有效:

delete from questions_tags
    where ctid not in (
        select min (ctid) as min_ctid 
            from questions_tags 
            group by question_id, tag_id
        );
Run Code Online (Sandbox Code Playgroud)