我正在寻找一个范围来返回所有没有特定关联的记录。
foo.rb
class Foo < ActiveRecord::Base
has_many :bars
end
bar.rb
class Bar < ActiveRecord::Base
belongs_to :foo
end
我想要一个可以找到所有Foo's
没有的范围bars
。使用 很容易找到具有关联的那些joins
,但我还没有找到相反的方法。
我正在寻找一个范围来返回所有没有特定关联的记录。
foo.rb
class Foo < ActiveRecord::Base
has_many :bars
end
bar.rb
class Bar < ActiveRecord::Base
belongs_to :foo
end
我想要一个可以找到所有Foo's
没有的范围bars
。使用 很容易找到具有关联的那些joins
,但我还没有找到相反的方法。
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) }
我有 100foos
和 9900 bars
。其中99 个foos
有 100 个bars
,其中一个没有。
Foo.left_outer_joins(:bars).where(bars: { foo_id: nil })
产生一个 SQL 查询:
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
当前接受的Foo.where.not(id: Bar.select(:foo_id).uniq)
答案不起作用。它产生两个 SQL 查询:
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
,因为所有foos
都有一个id
不为空的。
需要将其更改为Foo.where.not(id: Bar.pluck(:foo_id).uniq)
将其减少为一个查询并找到我们的Foo
,但它在基准测试中表现不佳
require 'benchmark/ips'
require_relative 'config/environment'
Benchmark.ips do |bm|
bm.report('left_outer_joins') do
Foo.left_outer_joins(:bars).where(bars: { foo_id: nil })
end
bm.report('where.not') do
Foo.where.not(id: Bar.pluck(:foo_id).uniq)
end
bm.compare!
end
Warming up --------------------------------------
left_outer_joins 1.143k i/100ms
where.not 6.000 i/100ms
Calculating -------------------------------------
left_outer_joins 13.659k (± 9.0%) i/s - 68.580k in 5.071807s
where.not 70.856 (± 9.9%) i/s - 354.000 in 5.057443s
Comparison:
left_outer_joins: 13659.3 i/s
where.not: 70.9 i/s - 192.77x slower
在 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
我更喜欢使用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}
是您可以用来构建请求范围的内容。
将 NOT EXISTS 与 LIMIT-ed 子查询一起使用会更快:
SELECT foos.* FROM foos
WHERE NOT EXISTS (SELECT id FROM bars WHERE bars.foo_id = foos.id LIMIT 1);
使用 ActiveRecord (>= 4.0.0):
Foo.where.not(Bar.where("bars.foo_id = foos.id").limit(1).arel.exists)
此方法利用includes
并允许范围链接。它应该适用于 Rails 5+
scope :barless, -> {
includes(
:bars
).where(
bars: {
id: nil
}
)
}