用于识别孤立子记录的 ActiveRecord 查询
ActiveRecord Query to identify orphan child records
我有 Post 和用户模型。每个 post 属于一个用户。但是,在数据库导入期间,在某些 post 上输入了一些错误的 user_ids。获取 post 的 user_ids 不引用任何用户的查询是什么?谢谢。
我不认为你可以用直接的 AR 来做到这一点,但它很容易修复 Ruby:
Post.find_each { |p| p.delete if p.user.nil? }
编辑:忘了 .all
不 return ActiveRecord::Relation
我会执行以下操作,这将导致一个 SELECT
和一个 DELETE
语句(因此总共有 2 个查询)
Post.where('`posts`.`user_id` NOT IN (?)', User.pluck(:id)).delete_all
谢谢你们。我的解决方案类似于 Manuel 的
all_user_ids = User.all.pluck(:id)
unwanted_posts = Post.where.not(:user_id => all_user_ids)
然后我可以摧毁所有unwanted_posts。当然,其他解决方案也可以。
桑杰,
所有建议的解决方案都适用于小表,但根据所涉及表的大小、内存量和可用处理能力,出于性能原因,您可能需要使用 LEFT OUTER JOIN,例如这个:
Post.joins("LEFT OUTER JOIN users ON posts.user_id = user.id")
.where("user.id IS NULL")
在Rails5中,有support for LEFT OUTER JOIN in ActiveRecord.
此致
正如@user2553863 已经提到的,Rails 5 添加了对 left_outer_joins
的支持,这意味着您现在可以高效地执行此操作,而无需像这样编写任何 SQL:
Post.left_outer_joins(:user).where(users: {id: nil}).delete_all
这将找到所有孤立的帖子(没有用户的帖子)并将其删除。这里,user
是关联名,users
是加入的table的名字。您不必触发额外的 SELECT
来查询所有用户 ID,当您有很多用户时,这可能会中断。
注意:以下答案适用于 Rails 5.0
其中许多答案适用于少数记录或小型表格,但根本无法很好地扩展到拥有大量孤立记录或处理大型表格时。
例如,处理两个较大的表,其中 ModelOne
有 707,891 条孤立记录:
irb(main):032:0> ModelOne.count
=> 2,265,216
irb(main):033:0> ModelTwo.count
=> 5,109,186
尝试使用 NOT IN
执行查询会失败,因为它太大了:
irb(main):029:0> ModelOne.where.not(model_two_id: ModelTwo.pluck(:id))
ActiveRecord::StatementInvalid (Mysql2::Error: MySQL server has gone away: SELECT `model_ones`.* FROM `model_ones` WHERE (`model_ones`.`model_two_id` NOT IN (12068663, 12076647, 12076648, 12082392, 12082393, 12082394, <repeat for the other 5 million ModelTwo records>))
此外,尝试在使用 left_outer_joins
的查询上调用 .delete_all
并不像预期的那样有效。
这是 SQL rails 为 ModelOne.left_outer_joins(:model_two).where(model_twos: {id: nil})
生成的:
SELECT `model_ones`.* FROM `model_ones`
LEFT OUTER JOIN `model_twos` ON `model_twos`.`id` = `model_ones`.`model_two_id`
WHERE `model_twos`.`id` IS NULL
但是将 .delete_all
链接到末尾 (ModelOne.left_outer_joins(:model_two).where(model_twos: {id: nil}).delete_all
) 会生成:
DELETE FROM `model_ones` WHERE `model_twos`.`id` IS NULL
这会引发错误。
我发现删除孤立记录的最高效方法来自 并使用 SQL EXISTS
和嵌套查询来有效地查找和删除孤立记录。
ModelOne.where.not(
ModelTwo.where('model_twos.id = model_ones.model_two_id').exists
)
生成:
SELECT `model_ones`.* FROM `model_ones`
WHERE (
NOT (
EXISTS (
SELECT `model_twos`.* FROM `model_twos` WHERE (model_twos.id = model_ones.model_two_id)
)
)
)
使用此查询加载 707,891 条孤立记录只需不到一分钟:
irb(main):040:0> Benchmark.measure { ModelOne.where.not(ModelTwo.where('model_twos.id = model_ones.model_two_id').exists).load }
=> #<Benchmark::Tms:0x0000563cfa227580 @label="", @real=59.61208474007435, @cstime=0.0, @cutime=0.0, @stime=0.23068100000000014, @utime=49.025859000000025, @total=49.25654000000002>
链接 .delete_all
到此查询将按预期工作并删除所有孤立记录
ModelOne.where.not(ModelTwo.where('model_twos.id = model_ones.model_two_id').exists).delete_all
生成 SQL:
DELETE FROM `model_ones` WHERE (NOT (EXISTS (SELECT `model_twos`.* FROM `model_twos` WHERE (model_twos.id = model_ones.model_two_id))))
RAILS 6.1+
您可以使用 'missing' 方法来获取孤立的记录。例如
Class User
end
Class Post
belongs_to :user
end
这里是使用 missing 方法的时候
Post.where.missing(:user)
这将获取具有 user_id 但相应用户被删除的所有 Post 记录。
我有 Post 和用户模型。每个 post 属于一个用户。但是,在数据库导入期间,在某些 post 上输入了一些错误的 user_ids。获取 post 的 user_ids 不引用任何用户的查询是什么?谢谢。
我不认为你可以用直接的 AR 来做到这一点,但它很容易修复 Ruby:
Post.find_each { |p| p.delete if p.user.nil? }
编辑:忘了 .all
不 return ActiveRecord::Relation
我会执行以下操作,这将导致一个 SELECT
和一个 DELETE
语句(因此总共有 2 个查询)
Post.where('`posts`.`user_id` NOT IN (?)', User.pluck(:id)).delete_all
谢谢你们。我的解决方案类似于 Manuel 的
all_user_ids = User.all.pluck(:id)
unwanted_posts = Post.where.not(:user_id => all_user_ids)
然后我可以摧毁所有unwanted_posts。当然,其他解决方案也可以。
桑杰,
所有建议的解决方案都适用于小表,但根据所涉及表的大小、内存量和可用处理能力,出于性能原因,您可能需要使用 LEFT OUTER JOIN,例如这个:
Post.joins("LEFT OUTER JOIN users ON posts.user_id = user.id")
.where("user.id IS NULL")
在Rails5中,有support for LEFT OUTER JOIN in ActiveRecord.
此致
正如@user2553863 已经提到的,Rails 5 添加了对 left_outer_joins
的支持,这意味着您现在可以高效地执行此操作,而无需像这样编写任何 SQL:
Post.left_outer_joins(:user).where(users: {id: nil}).delete_all
这将找到所有孤立的帖子(没有用户的帖子)并将其删除。这里,user
是关联名,users
是加入的table的名字。您不必触发额外的 SELECT
来查询所有用户 ID,当您有很多用户时,这可能会中断。
注意:以下答案适用于 Rails 5.0
其中许多答案适用于少数记录或小型表格,但根本无法很好地扩展到拥有大量孤立记录或处理大型表格时。
例如,处理两个较大的表,其中 ModelOne
有 707,891 条孤立记录:
irb(main):032:0> ModelOne.count
=> 2,265,216
irb(main):033:0> ModelTwo.count
=> 5,109,186
尝试使用 NOT IN
执行查询会失败,因为它太大了:
irb(main):029:0> ModelOne.where.not(model_two_id: ModelTwo.pluck(:id))
ActiveRecord::StatementInvalid (Mysql2::Error: MySQL server has gone away: SELECT `model_ones`.* FROM `model_ones` WHERE (`model_ones`.`model_two_id` NOT IN (12068663, 12076647, 12076648, 12082392, 12082393, 12082394, <repeat for the other 5 million ModelTwo records>))
此外,尝试在使用 left_outer_joins
的查询上调用 .delete_all
并不像预期的那样有效。
这是 SQL rails 为 ModelOne.left_outer_joins(:model_two).where(model_twos: {id: nil})
生成的:
SELECT `model_ones`.* FROM `model_ones`
LEFT OUTER JOIN `model_twos` ON `model_twos`.`id` = `model_ones`.`model_two_id`
WHERE `model_twos`.`id` IS NULL
但是将 .delete_all
链接到末尾 (ModelOne.left_outer_joins(:model_two).where(model_twos: {id: nil}).delete_all
) 会生成:
DELETE FROM `model_ones` WHERE `model_twos`.`id` IS NULL
这会引发错误。
我发现删除孤立记录的最高效方法来自 EXISTS
和嵌套查询来有效地查找和删除孤立记录。
ModelOne.where.not(
ModelTwo.where('model_twos.id = model_ones.model_two_id').exists
)
生成:
SELECT `model_ones`.* FROM `model_ones`
WHERE (
NOT (
EXISTS (
SELECT `model_twos`.* FROM `model_twos` WHERE (model_twos.id = model_ones.model_two_id)
)
)
)
使用此查询加载 707,891 条孤立记录只需不到一分钟:
irb(main):040:0> Benchmark.measure { ModelOne.where.not(ModelTwo.where('model_twos.id = model_ones.model_two_id').exists).load }
=> #<Benchmark::Tms:0x0000563cfa227580 @label="", @real=59.61208474007435, @cstime=0.0, @cutime=0.0, @stime=0.23068100000000014, @utime=49.025859000000025, @total=49.25654000000002>
链接 .delete_all
到此查询将按预期工作并删除所有孤立记录
ModelOne.where.not(ModelTwo.where('model_twos.id = model_ones.model_two_id').exists).delete_all
生成 SQL:
DELETE FROM `model_ones` WHERE (NOT (EXISTS (SELECT `model_twos`.* FROM `model_twos` WHERE (model_twos.id = model_ones.model_two_id))))
RAILS 6.1+
您可以使用 'missing' 方法来获取孤立的记录。例如
Class User
end
Class Post
belongs_to :user
end
这里是使用 missing 方法的时候
Post.where.missing(:user)
这将获取具有 user_id 但相应用户被删除的所有 Post 记录。