繁体   English   中英

Rails ActiveRecord WHERE EXISTS 查询

[英]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。
  • 选择一个 gem,比如activerecord_where_assoc ,它使语法更清晰,并且可以避免你犯简单的错误(比如没有像我原来的答案那样正确地确定你的 EXISTS 查询的范围)。

使用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM