我有一个名为HeroStatus的模型,它具有以下属性:
有超过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。哪一个?具有最近日期的那个。
我有三个问题:
如何使用仅SQL方法删除重复项?
如何使用纯Ruby解决方案删除重复项?与此类似:Removing "duplicate objects"。
如何进行验证以防止将来重复输入?
答案 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 ...”功能从潜在的重复中恢复。