ActiveRecord查询以识别孤立子记录

时间:2015-10-03 08:52:02

标签: ruby-on-rails-4 activerecord query-string postgresql-9.3

我有Post和User模型。每个帖子都属于一个用户。但是,在数据库导入期间,某些帖子上输入了一些错误的user_id。获取user_ids不引用任何用户的帖子的查询是什么?感谢。

7 个答案:

答案 0 :(得分:4)

桑杰,

所有提出的解决方案适用于小型表,但是根据所涉及的表的大小,以及可用的内存量和处理能力,出于性能原因,您可能希望使用LEFT OUTER JOIN,如下所示:

var app = angular.module('myApp', []);
app.controller('testCtrl', function($scope) {
$scope.company = [{id:1, name:"Sole Trader", rating:4},{id:2, name:"Pvt Ltd", rating:3},{id:3, name:"LLC", rating:5}];
$scope.Newrat = [];  
  $scope.addRatingbyId = function(pushdata){
  if($scope.Newrat.length != 0){
  angular.forEach($scope.Newrat, function(value, index){
  if(value.id == pushdata.id){
   $scope.Newrat[index].rating = pushdata.rating;
  }else{
   $scope.Newrat.push(pushdata);
   }
  });
 }else{
 $scope.Newrat.push(pushdata);
 }
  }
  $scope.sendRating= function(){
  //send $scope.Newrat  
  console.log($scope.Newrat);
  }
});

在Rails 5中,有support for LEFT OUTER JOIN in ActiveRecord

此致

答案 1 :(得分:4)

正如@ user2553863已经提到的,Rails 5添加了对left_outer_joins的支持,这意味着您现在可以以有效的方式执行此操作,而无需编写任何类似的SQL:

Post.left_outer_joins(:user).where(users: {id: nil}).delete_all

这将找到任何孤立的帖子(没有用户的帖子)并删除它们。这里,user是关联名称,users是连接表的名称。您不必再触发其他SELECT来查询所有用户ID,这可能会在您拥有多个用户时中断。

答案 2 :(得分:3)

我会执行以下操作,这将导致一个SELECT和一个DELETE语句(总共2个查询)

Post.where('`posts`.`user_id` NOT IN (?)', User.pluck(:id)).delete_all

答案 3 :(得分:2)

我认为你不能用直接的AR做到这一点,但用一点Ruby来修复它是相当容易的:

Post.find_each { |p| p.delete if p.user.nil? }

编辑:忘了.all没有返回ActiveRecord::Relation

答案 4 :(得分:2)

感谢你们两位。我的解决方案类似于Manuel的

all_user_ids = User.all.pluck(:id)
unwanted_posts = Post.where.not(:user_id => all_user_ids)

然后我可以销毁所有的unwanted_posts。当然,其他解决方案也可以。

答案 5 :(得分:2)

注意:以下答案对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>))

此外,尝试在使用.delete_all的查询上调用left_outer_joins并没有达到预期的效果。

这是为ModelOne.left_outer_joins(:model_two).where(model_twos: {id: nil})生成的SQL Rails:

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

这将引发错误。

我发现最有效的删除孤立记录的方法来自this answer,它使用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))))

答案 6 :(得分:0)

铁路6.1 +

您可以使用'missing'方法来获取孤立的记录。例如

Class User
end

Class Post
  belongs_to :user
end

这是使用缺少方法的时候

Post.where.missing(:user)

这将获取所有具有user_id但已删除相应用户的Post记录。