2016-05-29 15 views
3

Railsアプリケーションを使用して作成されたローカルPostgreSQLデータベースがあります。それには600k個のレコードがあり、そのうち〜200kは重複しています。私はレコードの1つだけを残し、重複を削除したい。 Railsは私の趣味であり、まだActiveRecordに苦労しています。ここでRails postgresデータベースから重複を削除する

は、私が見つけた方法です(Railsのコンソールで)重複:

Summary.select(:map_id).group(:map_id).having("count(*) > 1") 

私は単にそれがそのエントリのすべてのインスタンスを破壊するとして、その文の最後にdestroy_allを追加するなどのことができるとは思いません重複した値

重複を削除するように更新する方法を教えてください。

+1

SQLの周りであなたのやり方を知っているのであれば、SQLでやってみませんか? –

+0

なんらかの理由で、私は純粋なSQLをレールに使用するのが難しいと思っていました。私はこれをSQLで数回しました。 1つはmap_idによって順序付けられ、最初に選択されます。他の方法は、order by、countカラムの作成、countカラム= some_number(最初のものではなく2番目または3番目のものを保存したい場合に使用します)を選択することです。 – nonegiven72

+0

Railsでは生のSQLを使うのは簡単ですが、ActiveRecordはベビートークのSQLだけしか理解していないので、私はいつもやっています。 –

答えて

2

これは、各パスで、map_id当たり単一の複製を選択する、波のように重複を破壊します。重複が存在しなくなると、ループは自動的に終了します。

loop do 
    duplicates = Summary.select("MAX(id) as id, map_id").group(:map_id).having("count(*) > 1") 
    break if duplicates.length == 0 
    duplicates.destroy_all 
end 

データベースは次のようになります場合:最初の波で

| id | map_id | 
| 1 | 235 | 
| 2 | 299 | 
| 3 | 324 | 
| 4 | 235 | 
| 5 | 235 | 
| 6 | 299 | 
| 7 | 235 | 
| 8 | 324 | 
| 9 | 299 | 

、これらのレコードが返され、破壊される:第二波で

| id | map_id | 
| 7 | 235 | 
| 8 | 324 | 
| 9 | 299 | 

、このレコードは次のようになります返され、破壊された:

| id | map_id | 
| 5 | 235 | 
| 6 | 299 | 

第三の波は、このレコードを返し、破壊するであろう:

| id | map_id | 
| 4 | 235 | 

第四の波は、プロセスを完了します。与えられたmap_idに対して多数の重複がない限り、このプロセスは1桁のループ反復で終了する可能性があります。

このアプローチでは、重複のみが返され、より新しいの重複が削除されます。古い重複、代わりに、クエリがこれに変更することができます削除するには:その場合は

duplicates = Summary.select("MIN(id) as id, map_id").group(:map_id).having("count(*) > 1") 

を、波1が戻って破壊するであろう:

| id | map_id | 
| 1 | 235 | 
| 2 | 299 | 
| 3 | 324 | 

ウェーブ2は戻って破壊するであろう:

| id | map_id | 
| 4 | 235 | 
| 6 | 299 | 

ウェーブ3は戻って破壊するであろう:

| id | map_id | 
| 5 | 235 | 

Wave 4が処理を完了します。

+0

200k重複で少し時間がかかりますが、機能します。私は生成された複製の量を減らすために私の掻き取りロジックに取り組んでいます。 – nonegiven72

+0

聞いてよかったです!それ以降の実行でそれを使用するときは、* much *速くなければなりません。 200Kは多くのレコードを破壊するものです。 –

2

私は、DBコンソール(rails dbconsole)に行くとどうなる:

SELECT DISTINCT ON (map_id) * FROM summaries AS some_temp_name; 

は、その後のテーブルの名前を変更します。

EDIT - これはあなたが探しているもののように思える:

Summary.where.not(id: Summary.group(:map_id).pluck('min(summaries.id)')).delete_all 

テストされていません。この回答の一部です: Rails: Delete duplicate records based on multiple columns

+0

確かにうまくいくでしょうか。ウェブスクレイピングコードの一部であり、複数回実行されるので、あまりハックしないものがほしいと思っていました。 – nonegiven72

+0

@ nonegiven72:なぜこれを何度もやっていますか?おそらく、重複の混乱を取り除き、UNIQUE制約を追加してそれらが再び起きないようにしてから、追加/更新する前に重複をチェックして(制約から一意の違反例外をキャッチします) –

+0

私の他のアプリケーションはこれを持っていましたが、誰かがアカウントを作成したときにのみユニークなチェックが行われました。正直なところ、それほど頻繁ではありませんでした。私は1時間で500kのレコードを掻き集めるときに、それぞれが一意であるかどうかをチェックすることでプロセスが遅くなり、最後に削除するほうが簡単だと心配しました。 – nonegiven72

1

私が提案するのは、重複しているフィールドですべてのレコードと順序を取得することです。

次に、すべてのレコードをループし、値ごとに1つのレコードを保持します。

value = nil 
Summary.order("map_id ASC").each do |record| 
    if record.map_id == value 
    # duplicate 
    record.destroy 
    else 
    # first entry 
    value = record.map_id 
    end 
end 
+0

私は説明を得ますが、コードではvalue = nil部分は分かりません。 – nonegiven72

+0

ループの最初の反復で変数を 'nil'に初期化する必要があります。 –

関連する問題