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


9

我有一个具有这样的架构的表:

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"

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

SQL看起来像什么?

Answers:


15

根据我的经验(和在许多测试中所示)NOT IN证明由@gsiems是相当缓慢和规模苦头。逆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
   );

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

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

测试

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

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;

在这种情况下,索引无济于事。

结果

NOT IN
SQLfiddle超时。
在本地尝试了同样的操作,但几分钟后我也取消了。

EXISTS
在此SQLfiddle中完成半秒

备择方案

如果要删除大多数行,则将幸存者选择到另一个表中,将原始行拖放到幸存者的表中会更快捷。小心,如果在原始对象上定义了视图或外键(或其他依赖项),则可能会产生影响。

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

  • 删除所有外键和索引-以提高性能。
  • SELECT 幸存者到临时餐桌。
  • TRUNCATE 原本的。
  • 重新INSERT幸存者。
  • 重新CREATE索引和外键。视图可以保留,对性能没有影响。这里这里更多。

++表示存在的解决方案。比我的建议好得多。
gsiems

您能否在WHERE子句中解释ctid比较?
凯文·梅雷迪斯

1
@KevinMeredith:我添加了一些解释。
Erwin Brandstetter

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)

选择重复的数据:

=# 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)

删除重复的数据:

=# 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)

在您的情况下,以下方法应该起作用:

delete from questions_tags
    where ctid not in (
        select min (ctid) as min_ctid 
            from questions_tags 
            group by question_id, tag_id
        );

我在哪里可以了解更多信息ctid?谢谢。
marcamillion

@marcamillion-文档对ctid进行了简短介绍,网址为postgresql.org/docs/current/static/ddl-system-columns.html
gsiems 2013年

代表什么ctid
marcamillion

@marcamillion-tid ==“元组ID”,不确定c的含义。
gsiems 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.