2016-05-29 57 views
3

我有一個使用Rails應用程序創建的本地PostgreSQL數據庫。它有60萬條記錄,其中約200,000條是重複的。我只想保留一條記錄並刪除重複項。我每天都寫SQL來工作,但Rails是我的愛好,並且仍然與ActiveRecord糾纏。從Rails中刪除重複項postgres數據庫

以下是我找到了重複(在Rails的控制檯):

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

我不認爲我可以簡單地添加destroy_all到語句的結束,因爲它會破壞該條目的所有實例,其中包括重複的值。

你能告訴我如何更新它,以便它刪除重複?

+1

如果你知道你的SQL的方式,你爲什麼不只是在SQL呢? –

+0

出於某種原因,我認爲在rails中使用純SQL很困難。我在SQL中做了幾次。一個是按照map_id排序,然後選擇第一個。其他的可能是order by,創建一個count列,然後選擇column count = some_number(如果你不想要第一個,而是第二個或第三個觀察被保留)。 – 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有許多重複項,否則很可能這個過程將以單數位循環迭代完成。

鑑於此方法,將只會複製重複項,並且只會刪除重複的重複項。要刪除年長重複,相反,查詢可以改變這一點:

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

聽到這真是太棒了!當你在後續運行中使用它時,它應該*更快*。 200K是很多摧毀的記錄。 –

2

我會去到DB控制檯(rails dbconsole),並做到:

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

然後重命名錶。

編輯 - 這似乎是你在找什麼:

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

未經測試。這是這個答案的一部分在這裏: Rails: Delete duplicate records based on multiple columns

+0

肯定會工作,只是想要更少的hackish,因爲它將成爲網絡抓取代碼的一部分,從而不止一次執行。 – nonegiven72

+0

@ nonegiven72:你爲什麼會這樣做不止一次?想必你會清理重複的東西,添加一個UNIQUE約束來防止它們再次發生,然後在添加/更新之前檢查重複項(並從約束中捕獲唯一違規異常)。 –

+0

我的其他應用程序已經有了這個,但唯一的檢查是隻有當有人創建一個帳戶,老實說,這並不經常。我擔心,如果在一個小時內抓取500k條記錄,檢查每一條記錄的唯一性可能會減慢這一過程,並且最後刪除它們會更容易。 – nonegiven72

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'。 –