简体   繁体   English

Rails ActiveRecord WHERE EXISTS 查询

[英]Rails ActiveRecord WHERE EXISTS query

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".我有一个返回我需要的 SQL 查询,但我无法将其转换为“Rails 方式”的活动记录查询。

My SQL query is:我的 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%")

I'm trying something like this using Rails:我正在尝试使用 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.但它似乎不起作用,我不知道该尝试什么。

RAILS 5/6 EDIT: As of https://github.com/rails/rails/pull/29619 , Rails started discouraging the direct .exists call from my original answer. RAILS 5/6 编辑:https://github.com/rails/rails/pull/29619开始,Rails 开始不鼓励从我的原始答案中直接调用.exists I've updated it to use the new syntax, which invokes the arel proxy first ( .arel.exists ).我已经更新它以使用新语法,它首先调用arel代理( .arel.exists )。 Also, as of Rails 5, hash conditions work just fine within the EXISTS clause.此外,从 Rails 5 开始,哈希条件在 EXISTS 子句中工作得很好。

With all that taken into consideration, the pure ActiveRecord approach is now:考虑到所有这些,纯 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 )

If that looks a little scary, you do have some other options:如果这看起来有点吓人,您还有其他一些选择:

  • You could just use .where('EXISTS(SELECT 1 FROM places WHERE trip_id = trips.id AND name LIKE ?', '%Bangkok%') , embedding the SQL into your application directly. It's not as hip or cool, but in the long run it may be more maintainable -- it's very unlikely to be deprecated or stop working with future versions of rails.您可以使用.where('EXISTS(SELECT 1 FROM places WHERE trip_id = trips.id AND name LIKE ?', '%Bangkok%') ,将 SQL 直接嵌入到您的应用程序中。它不是那么时髦或酷,但在从长远来看,它可能更易于维护——它不太可能被弃用或停止使用未来版本的 rails。
  • Pick a gem, like activerecord_where_assoc , which makes the syntax cleaner and may save you from making simple mistakes (like not scoping your EXISTS query correctly, as my original answer did).选择一个 gem,比如activerecord_where_assoc ,它使语法更清晰,并且可以避免你犯简单的错误(比如没有像我原来的答案那样正确地确定你的 EXISTS 查询的范围)。

Using Where Exists gem (fair note: I'm its author):使用Where Exists gem(公平说明:我是它的作者):

Trip.where("trips.title LIKE ?", "%Thailand%")
  .where_exists(:places, ['name LIKE ?', '%Bangkok%'])
  .where_exists(:places, ['name LIKE ?', '%Phuket%'])

This is a refactored version of my first attempt that results much closer to your intended target (my apologies for the first, misleading one).这是我第一次尝试的重构版本,结果更接近您的预期目标(我对第一个误导性的人表示歉意)。

In app/models/trip.rb :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

Then you can write:然后你可以写:

Trip.for_title('Thailand').has_place('Bangkok').has_place('Phuket')

which will give the following SQL:这将给出以下 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'))

This allows you flexibility in how you piece together the query without having to rely on hardcoded SQL in your query and is portable between database engines.这使您可以灵活地将查询拼凑在一起,而不必依赖查询中的硬编码 SQL,并且可以在数据库引擎之间移植。

This solution assumes that that Place belongs_to Trip (and its complement).此解决方案假定 Place belongs_to Trip(及其补充)。 Adjust the joins clause if the Association(s) is/(are) different.如果关联不同,则调整joins子句。

This solution was inspired in part by How to do "where exists" in Arel and Make search NOT case sensitive on my rails app .该解决方案的部分灵感来自How to do "where exists" in Arel and Make search NOT casesensitive on my rails app

Not using a gem makes it much easier to make mistakes for this.不使用 gem 会更容易犯错。 The current accepted answers (as of writing this) does it manually and actually didn't generate the right SQL.当前接受的答案(在撰写本文时)是手动完成的,实际上并没有生成正确的 SQL。 (It has since been fixed, but that's highlights the risk for mistakes.) (此后已修复,但这突出了出错的风险。)

Another gem that exists to do that: activerecord_where_assoc (I'm the author)另一个存在的宝石: activerecord_where_assoc (我是作者)

With it, you can do what you want this way:有了它,你可以用这种方式做你想做的事:

Trip.where("trips.title LIKE ?", "%Thailand%")
    .where_assoc_exists(:places, ['name LIKE ?', '%Bangkok%'])
    .where_assoc_exists(:places, ['name LIKE ?', '%Phuket%'])

activerecord_where_assoc is more powerful, with full documentation , and CI tests against Rails 4.2 to 6.0. activerecord_where_assoc更强大,有完整的文档,以及针对 Rails 4.2 到 6.0 的 CI 测试。 Here is an introduction .这是一个介绍

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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