rails范围以检查关联是否不存在

时间:2012-04-27 17:12:12

标签: sql ruby-on-rails associations scopes

我期待编写一个范围,返回所有没有特定关联的记录。

foo.rb

class Foo < ActiveRecord::Base    
  has_many :bars
end

bar.rb

class Bar < ActiveRecord::Base    
  belongs_to :foo
end

我想要一个范围,可以找到拥有Foo's的所有bars。使用joins很容易找到具有关联的那些,但我还没有找到相反的方法。

6 个答案:

答案 0 :(得分:37)

Rails 4使这太容易了:)

Foo.where.not(id: Bar.select(:foo_id).uniq)

这会输出与jdoe的答案相同的查询

SELECT "foos".* 
FROM "foos" 
WHERE "foos"."id" NOT IN (
  SELECT DISTINCT "bars"."foo_id"
  FROM "bars" 
)

作为范围:

scope :lonely, -> { where.not(id: Bar.select(:item_id).uniq) }

答案 1 :(得分:18)

对于Rails 5+(Ruby 2.4.1&amp; Postgres 9.6)

我有100 array_multisort(array_column($myArray, 'count'), SORT_DESC, $myArray); 和9900 foosbars中的99个每个都有100个foos,其中一个没有。{/ p>

bars

生成一个SQL查询:

Foo.left_outer_joins(:bars).where(bars: { foo_id: nil })

并返回Foo Load (2.3ms) SELECT "foos".* FROM "foos" LEFT OUTER JOIN "bars" ON "bars"."foo_id" = "foos"."id" WHERE "bars"."foo_id" IS NULL ,但没有Foo

当前接受的答案bars 正在运作。它产生两个SQL查询:

Foo.where.not(id: Bar.select(:foo_id).uniq)

返回所有Bar Load (8.4ms) SELECT "bars"."foo_id" FROM "bars" Foo Load (0.3ms) SELECT "foos".* FROM "foos" WHERE ("foos"."id" IS NOT NULL) ,因为所有foos的{​​{1}}都不为空。

需要将其更改为foos以将其缩减为一个查询并找到我们的id,但它在基准测试中效果不佳

Foo.where.not(id: Bar.pluck(:foo_id).uniq)

答案 2 :(得分:13)

在foo.rb

class Foo < ActiveRecord::Base    
  has_many :bars
  scope :lonely, lambda { joins('LEFT OUTER JOIN bars ON foos.id = bars.foo_id').where('bars.foo_id IS NULL') }
end

答案 3 :(得分:9)

我更喜欢使用squeel gem来构建复杂的查询。它以如此神奇的方式扩展了ActiveRecord:

Foo.where{id.not_in Bar.select{foo_id}.uniq}

构建以下查询:

SELECT "foos".* 
FROM "foos" 
WHERE "foos"."id" NOT IN (
  SELECT DISTINCT "bars"."foo_id"
  FROM "bars" 
)

所以,

# in Foo class
scope :lonely, where{id.not_in Bar.select{foo_id}.uniq}

是您可以用来构建请求范围的。

答案 4 :(得分:5)

将NOT EXISTS与LIMIT-ed子查询一起使用可以更快:

SELECT foos.* FROM foos
WHERE NOT EXISTS (SELECT id FROM bars WHERE bars.foo_id = foos.id LIMIT 1);

使用ActiveRecord(&gt; = 4.0.0):

Foo.where.not(Bar.where("bars.foo_id = foos.id").limit(1).arel.exists)

答案 5 :(得分:0)

此方法利用includes并允许范围链接。它应该与Rails 5+一起使用

scope :barless, -> {
  includes(
    :bars
  ).where(
    bars: {
      id: nil
    }
  )
}