Jul*_*ina 23 sql ruby-on-rails associations scopes
我期待编写一个范围,返回所有没有特定关联的记录.
foo.rb
class Foo < ActiveRecord::Base
has_many :bars
end
Run Code Online (Sandbox Code Playgroud)
bar.rb
class Bar < ActiveRecord::Base
belongs_to :foo
end
Run Code Online (Sandbox Code Playgroud)
我希望有一个范围,可以找到所有的Foo's那不要有任何bars.很容易找到使用关联的那些joins,但我还没有找到相反的方法.
dav*_*son 39
Rails 4让这太容易:)
Foo.where.not(id: Bar.select(:foo_id).uniq)
Run Code Online (Sandbox Code Playgroud)
这输出与jdoe的答案相同的查询
SELECT "foos".*
FROM "foos"
WHERE "foos"."id" NOT IN (
SELECT DISTINCT "bars"."foo_id"
FROM "bars"
)
Run Code Online (Sandbox Code Playgroud)
作为范围:
scope :lonely, -> { where.not(id: Bar.select(:item_id).uniq) }
Run Code Online (Sandbox Code Playgroud)
m. *_*org 20
我有100 foos和9900 bars.foos每个中有99个有100个bars,其中一个没有.
Foo.left_outer_joins(:bars).where(bars: { foo_id: nil })
Run Code Online (Sandbox Code Playgroud)
生成一个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
Run Code Online (Sandbox Code Playgroud)
并返回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)
Run Code Online (Sandbox Code Playgroud)
返回all,foos因为所有foos都有一个id非null.
需要将其更改为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
Run Code Online (Sandbox Code Playgroud)
shw*_*eta 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
Run Code Online (Sandbox Code Playgroud)
我更喜欢使用squeel gem来构建复杂的查询.它以如此神奇的方式扩展了ActiveRecord:
Foo.where{id.not_in Bar.select{foo_id}.uniq}
Run Code Online (Sandbox Code Playgroud)
构建以下查询:
SELECT "foos".*
FROM "foos"
WHERE "foos"."id" NOT IN (
SELECT DISTINCT "bars"."foo_id"
FROM "bars"
)
Run Code Online (Sandbox Code Playgroud)
所以,
# in Foo class
scope :lonely, where{id.not_in Bar.select{foo_id}.uniq}
Run Code Online (Sandbox Code Playgroud)
是您可以用来构建请求的范围.
使用NOT EXISTS和LIMIT-ed子查询可以更快:
SELECT foos.* FROM foos
WHERE NOT EXISTS (SELECT id FROM bars WHERE bars.foo_id = foos.id LIMIT 1);
Run Code Online (Sandbox Code Playgroud)
使用ActiveRecord(> = 4.0.0):
Foo.where.not(Bar.where("bars.foo_id = foos.id").limit(1).arel.exists)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
14816 次 |
| 最近记录: |