次のようなスキーマを持つテーブルがあります。
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_id
およびquestion_id
を別のレコードとして。
そのためにSQLはどのように見えますか?
私の経験では(多くのテストで示されているように)NOT IN
as @ 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
は、 every 行に存在し、必ず一意である内部タプル識別子です。参考文献:
私はあなたの質問と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
これで0.5秒で終了します SQLfiddle 。
Ifほとんどの行を削除する場合、生存者を別のテーブルに選択し、元のテーブルを削除して生存者のテーブルの名前を変更する方が高速です。元のビューまたは外部キー(または他の依存関係)が定義されている場合、注意が必要です。
依存関係があり、それらを保持したい場合は、次のことができます。
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
);