I have an SQL query that returns what I need, but I'm having trouble converting this into an active record query the "Rails way".
My SQL query is:
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%")
I'm trying something like this using 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)
But it doesn't seem to work and i'm stumped as to what to try.
答案 0 :(得分:4)
使用Where Exists gem(公平说明:我是作者):
Trip.where("trips.title LIKE ?", "%Thailand%")
.where_exists(:places, ['name LIKE ?', '%Bangkok%'])
.where_exists(:places, ['name LIKE ?', '%Phuket%'])
答案 1 :(得分:1)
这可以做到,但它有点不可思议:
Trip.where("trips.title LIKE ?", "%Thailand%")
.joins(:places)
.where( Place.where('name LIKE ?', '%Bangkok%').exists )
.where( Place.where('name LIKE ?', '%Phuket%').exists )
我说" wonky"因为您的where
条件只能包含文字字符串;使用像where(name: 'Phuket')
这样的哈希语法会使Rails barf变得奇怪:
> User.joins(:groups).where( Group.where(name: 'a').exists )
User Load (3.2ms) SELECT "users".* FROM "users" INNER JOIN "groups" ON "groups"."user_id" = "users"."id" WHERE (EXISTS (SELECT "groups".* FROM "groups" WHERE "groups"."name" = $1))
PG::ProtocolViolation: ERROR: bind message supplies 0 parameters, but prepared statement "" requires 1
: SELECT "users".* FROM "users" INNER JOIN "groups" ON "groups"."user_id" = "users"."id" WHERE (EXISTS (SELECT "groups".* FROM "groups" WHERE "groups"."name" = $1))
ActiveRecord::StatementInvalid: PG::ProtocolViolation: ERROR: bind message supplies 0 parameters, but prepared statement "" requires 1
: SELECT "users".* FROM "users" INNER JOIN "groups" ON "groups"."user_id" = "users"."id" WHERE (EXISTS (SELECT "groups".* FROM "groups" WHERE "groups"."name" = $1))
该错误来自我所处的随机数据模型,但我预计会出现同样的错误。不幸的是,Rails在处理嵌套查询时搞砸了它如何绑定参数。
另一种方法是使用.where('EXISTS(SELECT 1 FROM places WHERE name LIKE ?', '%Bangkok%')
,直接将SQL嵌入到您的应用程序中。它不像时髦或酷,但从长远来看,它可能更容易维护!
答案 2 :(得分:0)
这是我第一次尝试的重构版本,其结果更接近您的预期目标(我对第一次,误导性的目标道歉)。
在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,并且可以在数据库引擎之间移植。
此解决方案假定放置belongs_to
Trip(及其补码)。如果关联是/(不同),请调整joins
子句。
此解决方案的灵感部分来自How to do "where exists" in Arel和Make search NOT case sensitive on my rails app。
答案 3 :(得分:0)
不使用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具有full documentation的强大功能,并且针对Rails 4.2到6.0进行了CI测试。这是introduction。