删除由多个属性唯一标识的重复记录/对象

时间:2010-05-07 20:10:02

标签: mysql ruby-on-rails ruby validation activerecord

我有一个名为HeroStatus的模型,它具有以下属性:

  • ID
  • USER_ID
  • recordable_type
  • hero_type(可以为NULL!)
  • recordable_id
  • created_at

有超过100个hero_statuses,一个用户可以拥有多个hero_statuses,但不能多次拥有相同的hero_status。

用户的hero_status由recordable_type + hero_type + recordable_id的组合唯一标识。我想说的是,特定用户不能有重复的hero_status。

不幸的是,我没有进行确认以确保这一点,所以在我做了一些代码更改之后,我为用户提供了一些重复的hero_statuses。例如:

user_id = 18
recordable_type = 'Evil'
hero_type = 'Halitosis'
recordable_id = 1
created_at = '2010-05-03 18:30:30'

user_id = 18
recordable_type = 'Evil'
hero_type = 'Halitosis'
recordable_id = 1
created_at = '2009-03-03 15:30:00'

user_id = 18
recordable_type = 'Good'
hero_type = 'Hugs'
recordable_id = 1
created_at = '2009-02-03 12:30:00'

user_id = 18
recordable_type = 'Good'
hero_type = NULL
recordable_id = 2
created_at = '2009-012-03 08:30:00'

(最后两个显然不是重复。前两个是。)所以我想做的是摆脱重复的hero_status。哪一个?具有最近日期的那个。

我有三个问题:

  1. 如何使用仅SQL方法删除重复项?

  2. 如何使用纯Ruby解决方案删除重复项?与此类似:Removing "duplicate objects"

  3. 如何进行验证以防止将来重复输入?

2 个答案:

答案 0 :(得分:1)

对于仅SQL的方法,我会使用此查询 - (我假设id是唯一的。)

DELETE FROM HeroStatus WHERE id IN
(SELECT id FROM 
   (SELECT user_id, recordable_type, hero_type, recordable_id, MAX(created_at)
     GROUP BY del.user_id, recordable_type, hero_type, recordable_id
     HAVING Count(id)>1) AS del 
      INNER JOIN HeroStatus AS hs ON
      hs.user_id=del.user_id AND hs.recordable_type=del.recordable_type 
       AND hs.hero_type=del.hero_type AND hs.recordable_id=del.recordable_id 
       AND hs.created_at = del.created_at)

有点怪物!查询使用自然键(user_id,recordable_type,hero_type)查找所有重复项,并选择具有最大created_at值的值(最近创建的)。然后它会找到这些行的ID(通过连接回主表)并删除具有该id的行。

(请先在表格的副本上试试这个并确认你得到你想要的结果!: - )

为了防止将来发生这种情况,请在列user_id,recordable_type,hero_type,recordable_id上​​添加唯一索引或约束。 E.g。

ALTER TABLE HeroStatus 
ADD UNIQUE (user_id, recordable_type, hero_type, recordable_id)

修改

您可以在以下迁移中添加(并删除)此索引:

add_index(:HeroStatus, [:user_id, :recordable_type, :hero_type, :recordable_id], :unique => true)
remove_index(:HeroStatus, :column => [:user_id, :recordable_type, :hero_type, :recordable_id], :unique => true)

或者,如果您想明确命名它:

add_index(:HeroStatus, [:user_id, :recordable_type, :hero_type, :recordable_id], :unique => true, :name => :my_unique_index)
remove_index(:HeroStatus, :name => :my_unique_index)

答案 1 :(得分:0)

有时你需要卷起袖子并做一些严肃的SQL来杀死你不想要的所有东西。如果它是一个单击的东西,这很容易,并且不太难以滚动到你可以按需发射的Rake任务。

例如,要选择所有不同的状态记录,可以使用以下内容:

SELECT id FROM hero_statuses GROUP BY user_id, hero_type, recordable_id

鉴于这些是您的集合中足够独特的记录,您可以去除所有您不想要的记录:

DELETE FROM hero_statuses WHERE id NOT IN (SELECT id FROM hero_statuses GROUP BY user_id, hero_type, recordable_id)

与涉及DELETE FROM的任何操作一样,我希望您不要在没有通常的预防措施的情况下解决生产数据。

至于将来如何防止这种情况,如果这些是唯一约束,请在它们上创建一个唯一索引:

add_index :hero_statuses, [ :user_id, :hero_type, :recordable_id ], :unique => true

当您尝试引入重复记录时,这将生成ActiveRecord异常。唯一索引的一个好处是,您可以使用“INSERT IGNORE INTO ...”或“INSERT ... ON DUPLICATE KEY ...”功能从潜在的重复中恢复。