Подзапросы в activerecord - PullRequest
       15

Подзапросы в activerecord

77 голосов
/ 30 марта 2011

С помощью SQL я могу легко выполнять такие подзапросы, как этот

User.where(:id => Account.where(..).select(:user_id))

. Это приводит к:

SELECT * FROM users WHERE id IN (SELECT user_id FROM accounts WHERE ..)

Как я могу сделать это, используя рельсы 3 activerecord / arel / meta_where?

Мне нужны / нужны настоящие подзапросы, без обходных путей (используя несколько запросов).

Ответы [ 5 ]

118 голосов
/ 31 мая 2012

Rails теперь делает это по умолчанию:)

Message.where(user_id: Profile.select("user_id").where(gender: 'm'))

выдаст следующий SQL

SELECT "messages".* FROM "messages" WHERE "messages"."user_id" IN (SELECT user_id FROM "profiles" WHERE "profiles"."gender" = 'm')

(номер версии, на которую ссылается «сейчас», скорее всего, 3.2)

40 голосов
/ 19 апреля 2011

В ARel методы where() могут принимать массивы в качестве аргументов, которые будут генерировать запрос "WHERE id IN ...".Итак, то, что вы написали, находится в правильных строках.

Например, следующий код ARel:

User.where(:id => Order.where(:user_id => 5)).to_sql

... что эквивалентно:

User.where(:id => [5, 1, 2, 3]).to_sql

... выдает следующий SQL в базе данных PostgreSQL:

SELECT "users".* FROM "users" WHERE "users"."id" IN (5, 1, 2, 3)" 

Обновление: в ответ на комментарии

Хорошо, поэтому я неправильно понял вопрос.Я считаю, что вы хотите, чтобы подзапрос явно перечислял имена столбцов, которые должны быть выбраны, чтобы не попадать в базу данных двумя запросами (что делает ActiveRecord в простейшем случае).

Вы можетеиспользуйте project для select в вашем подвыборе:

accounts = Account.arel_table
User.where(:id => accounts.project(:user_id).where(accounts[:user_id].not_eq(6)))

... что приведет к следующему SQL:

SELECT "users".* FROM "users" WHERE "users"."id" IN (SELECT user_id FROM "accounts" WHERE "accounts"."user_id" != 6)

Я искренне надеюсь, что далВы, что вы хотели на этот раз!

23 голосов
/ 03 января 2012

Я сам искал ответ на этот вопрос и придумал альтернативный подход.Я просто подумал, что поделюсь этим - надеюсь, это кому-нибудь поможет!:)

# 1. Build you subquery with AREL.
subquery = Account.where(...).select(:id)
# 2. Use the AREL object in your query by converting it into a SQL string
query = User.where("users.account_id IN (#{subquery.to_sql})")

Бинго!Bango!

Работает с Rails 3.1

0 голосов
/ 15 февраля 2019

Это пример вложенного подзапроса с использованием rails ActiveRecord и с использованием JOIN, где вы можете добавлять предложения для каждого запроса, а также результат:

Вы можете добавить вложенные области inner_query и external_query в файл модели и использовать ...

  inner_query = Account.inner_query(params)
  result = User.outer_query(params).joins("(#{inner_query.to_sql}) alias ON users.id=accounts.id")
   .group("alias.grouping_var, alias.grouping_var2 ...")
   .order("...")

Пример области применения:

   scope :inner_query , -> (ids) {
    select("...")
    .joins("left join users on users.id = accounts.id")
    .where("users.account_id IN (?)", ids)
    .group("...")
   }
0 голосов
/ 26 сентября 2018

Другая альтернатива:

Message.where(user: User.joins(:profile).where(profile: { gender: 'm' })
...