假设我有一个User
和一个用户has_many :tags
,并且我想删除所有重复@users
的{{1}}标签。例如,
name
我只想保留唯一名称的标签,其余的从数据库中删除。
我知道我可以从用户标签中提取出唯一标签名称列表,然后删除所有用户标签,然后仅使用唯一名称重新创建用户标签,但这会效率低下吗?
另一方面,@user.tags #=> [<Tag name: 'A'>, <Tag name: 'A'>, <Tag name: 'B'>]
将不起作用,因为它仅返回选定的列。 select
也将不起作用:
uniq
有没有更有效的方法?
更新: 我想在迁移中做到这一点。
答案 0 :(得分:3)
此方法将为您提供ActiveRecord ::与重复标签的关系:
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
答案 1 :(得分:1)
您可以在迁移中编写与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
This SQL fiddle节目
您可以根据自己的目标在DELETE
查询中用作 sub-select 的不同查询:删除第一个/最后一个/全部重复项。