【问题标题】:Remove duplicate records/objects uniquely identified by multiple attributes删除由多个属性唯一标识的重复记录/对象
【发布时间】:2010-05-07 20:10:02
【问题描述】:

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

  • 身份证
  • user_id
  • recordable_type
  • hero_type(可以为 NULL!)
  • recordable_id
  • created_at

有超过 100 个 hero_statuse,一个用户可以拥有多个 hero_status,但不能多次拥有同一个 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. 如何进行验证以防止将来出现重复条目​​?

【问题讨论】:

  • 这不应该是一个社区维基。
  • 所以下次我会知道,你应该什么时候加入,什么时候不应该加入?

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


【解决方案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 列上添加唯一索引或约束。例如

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)

【讨论】:

  • 如何给它起一个名字,以便在回滚迁移中删除它?
  • 很抱歉我无法回答这个问题 - 我不了解 Ruby。也许其他人可以介入?
【解决方案2】:

有时您需要卷起袖子,执行一些严肃的 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 ...”功能从潜在的重复中恢复。

【讨论】:

  • AFAIK,标准 SQL 不允许选择您在 GROUP 子句中没有的列。除了在 MySQL 之外,它还能在哪里工作?
  • Cld 我在模型中做这样的事情进行验证? validates_uniqueness_of :user_id, :scope => :recordable_type, :hero_type, :recordable_id 它必须起作用,以便多个用户可以拥有相同的 hero_status,但单个用户不能多次出现 hero_status。
  • 这是一个 MySQL 问题,所以我不太担心查询中不存在 GROUP BY 列。另一个 RDBMS 可能不支持这一点,但我相信这正在被其他平台所接受。
  • 就验证范围而言,不要忘记将它们括在方括号中,它应该可以工作: :scope => [ :x, :y, :z ]
猜你喜欢
  • 2021-02-22
  • 1970-01-01
  • 2014-09-11
  • 2017-02-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-13
  • 2023-04-02
相关资源
最近更新 更多