![](/img/trans.png)
[英]Rails 3.2 - ActiveRecord `where` query with method called on attribute
[英]Rails ActiveRecord WHERE EXISTS query
我有一个返回我需要的 SQL 查询,但我无法将其转换为“Rails 方式”的活动记录查询。
我的 SQL 查询是:
SELECT * from trips
WHERE trips.title LIKE "%Thailand%"
AND EXISTS (SELECT * from places WHERE places.trip_id = trips.id AND places.name LIKE "%Bangkok%")
AND EXISTS (SELECT * from places WHERE places.trip_id = trips.id AND places.name LIKE "%Phuket%")
我正在尝试使用 Rails 进行类似的操作:
@trips=Trip.where("trips.title LIKE ?", "%Thailand%")
@trips=@trips.includes(:places).where(("places.name LIKE ?","%Bangkok%").exists?) => true, ("places.name LIKE ?","%Phuket%").exists?) => true)
但它似乎不起作用,我不知道该尝试什么。
RAILS 5/6 编辑:从https://github.com/rails/rails/pull/29619开始,Rails 开始不鼓励从我的原始答案中直接调用.exists
。 我已经更新它以使用新语法,它首先调用arel
代理( .arel.exists
)。 此外,从 Rails 5 开始,哈希条件在 EXISTS 子句中工作得很好。
考虑到所有这些,纯 ActiveRecord 方法现在是:
Trip.where("trips.title LIKE ?", "%Thailand%")
.where( Place.where('trip_id = trips.id AND name LIKE ?', '%Bangkok%').arel.exists )
.where( Place.where('trip_id = trips.id AND name LIKE ?', '%Phuket%').arel.exists )
如果这看起来有点吓人,您还有其他一些选择:
.where('EXISTS(SELECT 1 FROM places WHERE trip_id = trips.id AND name LIKE ?', '%Bangkok%')
,将 SQL 直接嵌入到您的应用程序中。它不是那么时髦或酷,但在从长远来看,它可能更易于维护——它不太可能被弃用或停止使用未来版本的 rails。使用Where Exists gem(公平说明:我是它的作者):
Trip.where("trips.title LIKE ?", "%Thailand%")
.where_exists(:places, ['name LIKE ?', '%Bangkok%'])
.where_exists(:places, ['name LIKE ?', '%Phuket%'])
这是我第一次尝试的重构版本,结果更接近您的预期目标(我对第一个误导性的人表示歉意)。
在app/models/trip.rb
:
scope :with_title, ->(title) { where(arel_table[:title].matches('%' + title + '%')) }
def self.has_place(place_name)
_trips = Trip.arel_table
_places = Place.arel_table
where(Place.joins(:trip).where(_places[:name].eq(place_name)).exists)
end
然后你可以写:
Trip.for_title('Thailand').has_place('Bangkok').has_place('Phuket')
这将给出以下 SQL:
SELECT "trips".* FROM "trips" WHERE ("trips"."title" LIKE '%Thailand%') AND (EXISTS (SELECT "places".* FROM "places" INNER JOIN "trips" ON "trips"."id" = "places"."trip_id" WHERE "places"."name" = 'Bangkok')) AND (EXISTS (SELECT "places".* FROM "places" INNER JOIN "trips" ON "trips"."id" = "places"."trip_id" WHERE "places"."name" = 'Phuket'))
这使您可以灵活地将查询拼凑在一起,而不必依赖查询中的硬编码 SQL,并且可以在数据库引擎之间移植。
此解决方案假定 Place belongs_to
Trip(及其补充)。 如果关联不同,则调整joins
子句。
该解决方案的部分灵感来自How to do "where exists" in Arel and Make search NOT casesensitive on my rails app 。
不使用 gem 会更容易犯错。 当前接受的答案(在撰写本文时)是手动完成的,实际上并没有生成正确的 SQL。 (此后已修复,但这突出了出错的风险。)
另一个存在的宝石: activerecord_where_assoc (我是作者)
有了它,你可以用这种方式做你想做的事:
Trip.where("trips.title LIKE ?", "%Thailand%")
.where_assoc_exists(:places, ['name LIKE ?', '%Bangkok%'])
.where_assoc_exists(:places, ['name LIKE ?', '%Phuket%'])
activerecord_where_assoc更强大,有完整的文档,以及针对 Rails 4.2 到 6.0 的 CI 测试。 这是一个介绍。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.