特定の関連付けを持たないすべてのレコードを返すスコープを作成することを検討しています。
foo.rb
class Foo < ActiveRecord::Base
has_many :bars
end
bar.rb
class Bar < ActiveRecord::Base
belongs_to :foo
end
dontにbars
があるすべてのFoo's
を見つけることができるスコープが必要です。 joins
を使用して関連付けがあるものを見つけるのは簡単ですが、その逆を行う方法は見つかりませんでした。
Rails4ではこれが簡単すぎます:)
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) }
100 foos
と9900bars
があります。 99個のfoos
にはそれぞれ100個のbars
があり、そのうちの1個にはありません。
_Foo.left_outer_joins(:bars).where(bars: { foo_id: nil })
_
1つの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
なしで1つのbars
を返します
現在受け入れられている回答Foo.where.not(id: Bar.select(:foo_id).uniq)
はnot機能しています。 2つの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
にはnullではないfoos
があるため、これはすべてのid
を返します。
1つのクエリに減らしてFoo
を見つけるには、Foo.where.not(id: Bar.pluck(:foo_id).uniq)
に変更する必要がありますが、ベンチマークではパフォーマンスが低下します。
_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}
要求されたスコープを構築するために使用できるものです。
LIMIT-edサブクエリでNOTEXISTSを使用すると、より高速になります。
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)