Для подхода только на SQL я бы использовал этот запрос - (я предполагаю, что идентификаторы уникальны.)
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
(последний создан). Затем он находит идентификаторы этих строк (путем присоединения к основной таблице) и удаляет строки с этим идентификатором.
(Пожалуйста, сначала попробуйте это на копии таблицы и убедитесь, что вы получите желаемый результат!: -)
Чтобы предотвратить это в будущем, добавьте уникальный индекс или ограничение по столбцам user_id, recordable_type, hero_type, recordable_id. Э.Г.
ALTER TABLE HeroStatus
ADD UNIQUE (user_id, recordable_type, hero_type, recordable_id)
EDIT:
Вы добавляете (и удаляете) этот индекс в рамках миграции следующим образом:
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)