Ruby on rails 4 用于标识孤立子记录的ActiveRecord查询
我有Post和用户模型。每个帖子都属于一个用户。但是,在数据库导入过程中,在某些帖子上输入了一些错误的用户ID。如果要获取用户ID不指向任何用户的帖子,查询将是什么?谢谢。我不认为你可以用straight AR实现这一点,但用一点Ruby就可以很容易地修复:Ruby on rails 4 用于标识孤立子记录的ActiveRecord查询,ruby-on-rails-4,activerecord,query-string,postgresql-9.3,Ruby On Rails 4,Activerecord,Query String,Postgresql 9.3,我有Post和用户模型。每个帖子都属于一个用户。但是,在数据库导入过程中,在某些帖子上输入了一些错误的用户ID。如果要获取用户ID不指向任何用户的帖子,查询将是什么?谢谢。我不认为你可以用straight AR实现这一点,但用一点Ruby就可以很容易地修复: Post.find_each { |p| p.delete if p.user.nil? } 编辑:忘记。all不会返回ActiveRecord::Relation我将执行以下操作,这将导致一个SELECT和一个DELETE语句(总共2个
Post.find_each { |p| p.delete if p.user.nil? }
编辑:忘记
。all
不会返回ActiveRecord::Relation
我将执行以下操作,这将导致一个SELECT
和一个DELETE
语句(总共2个查询)
谢谢你们两位。我的解决方案与曼纽尔的类似
all_user_ids = User.all.pluck(:id)
unwanted_posts = Post.where.not(:user_id => all_user_ids)
然后我可以销毁所有不需要的帖子。当然,另一种解决方案也会奏效 桑杰
所有建议的解决方案都适用于小型表,但根据所涉及表的大小、可用内存量和处理能力,出于性能原因,您可能希望使用左外部联接,如下所示:
Post.joins("LEFT OUTER JOIN users ON posts.user_id = user.id")
.where("user.id IS NULL")
Post.left_outer_joins(:user).where(users: {id: nil}).delete_all
在Rails 5中,有一个
正如前面提到的@user2553863一样,Rails 5增加了对的支持,这意味着您现在可以高效地完成这项工作,而无需编写任何类似以下的SQL:
Post.joins("LEFT OUTER JOIN users ON posts.user_id = user.id")
.where("user.id IS NULL")
Post.left_outer_joins(:user).where(users: {id: nil}).delete_all
这将找到任何孤立的帖子(没有用户的帖子)并删除它们。这里,
user
是关联名称,users
是联接表的名称。您不必启动额外的选择来查询所有用户ID,当您有许多用户时,这些ID可能会中断。注意:以下答案对Rails 5.0有效
这些答案中的许多对于一些记录或小表都适用,但对于拥有大量孤立记录或处理大表来说,它们的扩展性并不好
例如,处理两个较大的表,其中ModelOne
有707891条孤立记录:
irb(main):032:0> ModelOne.count
=> 2,265,216
irb(main):033:0> ModelTwo.count
=> 5,109,186
尝试执行不在
中的查询将失败,因为该查询太大:
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>))
但是将.delete\u all
链接到末尾(ModelOne.left\u outer\u连接(:model\u two)。其中(model\u twos:{id:nil})。delete\u all
)生成:
DELETE FROM `model_ones` WHERE `model_twos`.`id` IS NULL
SELECT `model_ones`.* FROM `model_ones`
WHERE (
NOT (
EXISTS (
SELECT `model_twos`.* FROM `model_twos` WHERE (model_twos.id = model_ones.model_two_id)
)
)
)
这将抛出一个错误
我发现删除孤立记录最有效的方法来自并使用SQLEXISTS
和嵌套查询来高效地查找和删除孤立记录
ModelOne.where.not(
ModelTwo.where('model_twos.id = model_ones.model_two_id').exists
)
由此产生:
DELETE FROM `model_ones` WHERE `model_twos`.`id` IS NULL
SELECT `model_ones`.* FROM `model_ones`
WHERE (
NOT (
EXISTS (
SELECT `model_twos`.* FROM `model_twos` WHERE (model_twos.id = model_ones.model_two_id)
)
)
)
使用此查询加载707891孤立记录只需不到一分钟的时间:
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>
生成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)
这将获取所有具有用户id但相应用户被删除的Post记录。这不是两次选择吗?一个用于Pull,一个用于“where”语句?发现了一个有趣的警告——只有存在一些非孤儿记录时,这才有效。如果它们都是孤立项,ActiveRecord最终会使用子句posts.user\u id NOT IN(NULL)
执行查询,即使存在记录,该子句实际上也不会返回任何内容。下面的加入推荐@user2553863状态是最可靠的。谢谢。这将解决阵列可能导致的内存问题。谢谢!好提示!