Rails удаляет дублирующиеся связанные записи - PullRequest
0 голосов
/ 15 января 2019

Допустим, у меня есть User и пользователь has_many :tags, и я хотел бы удалить все теги @users, которые дублировали name. Например,

@user.tags #=> [<Tag name: 'A'>, <Tag name: 'A'>, <Tag name: 'B'>]

Я бы хотел сохранить только теги с уникальными именами и удалить остальные из базы данных.

Я знаю, что мог бы вытащить список имен уникальных тегов из тегов пользователя, удалить все теги пользователей и заново создать теги пользователей только с уникальными именами, но это было бы неэффективно?

С другой стороны, select не будет работать, поскольку возвращает только выбранный столбец. uniq также не будет работать:

@user.tags.uniq #=> returns all tags

Есть ли более эффективный способ?

UPDATE: Я хотел бы сделать это во время миграции.

Ответы [ 2 ]

0 голосов
/ 15 января 2019

Вы можете написать независимый от модели запрос SQL в процессе миграции. Вот специфичный для PostgreSQL код миграции:

execute <<-SQL
  DELETE FROM tags
  WHERE id NOT IN (
    SELECT DISTINCT ON(user_id, name) id FROM tags
    ORDER BY user_id, name, id ASC
  )
SQL

А вот еще один типичный SQL:

execute <<-SQL
  DELETE FROM tags
  WHERE id IN (
    SELECT DISTINCT t2.id FROM tags t1
    INNER JOIN tags t2
    ON (
      t1.user_id = t2.user_id AND
      t1.name = t2.name AND
      t1.id < t2.id
    )
  )
SQL

Эта SQL-скрипка показывает различные запросы, которые вы можете использовать в качестве суб-выбора в DELETE запросе в зависимости от ваших целей: удаление первого / последнего / всех дубликатов.

0 голосов
/ 15 января 2019

Этот метод даст вам ActiveRecord :: Relation с дублирующимися тегами:

class Tag < ApplicationRecord
  belongs_to :user

  def self.duplicate_tags
    unique = self.select('DISTINCT ON(tags.name, tags.user_id) tags.id')
     .order(:name, :user_id, :id)
    self.where.not(id: unique)
  end
end

На самом деле он запускается как один запрос:

SELECT  "tags".* FROM "tags" 
WHERE "tags"."id" NOT IN 
 (SELECT DISTINCT ON(tags.name) tags.id 
  FROM "tags" GROUP BY "tags"."id", "tags"."user_id" 
  ORDER BY tags.name, tags.id)

Вы можете удалить дубликаты в одном запросе с помощью #delete_all.

# Warning! This can't be undone!
Tag.duplicate_tags.destroy_all

Если вам нужно уничтожить зависимые ассоциации или вызвать обратные вызовы before_* или after_destroy, используйте вместо этого метод #destroy_all. Но вы должны использовать это вместе с #in_batches, чтобы избежать нехватки памяти.

# Warning! This can't be undone!
Tag.duplicate_tags.in_batches do |batch|
  # destroys a batch of 1000 records
  batch.destroy_all
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...