Rails ActiveRecord WHERE EXISTS query

时间:2015-07-31 20:39:41

标签: mysql ruby-on-rails

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.

4 个答案:

答案 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 ArelMake 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