activerecordのサブクエリ


82

SQLを使用すると、このようなサブクエリを簡単に実行できます

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

これにより、次のものが生成されます。

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

Railsの3activerecord / arel / meta_whereを使用してこれを行うにはどうすればよいですか?

私は実際のサブクエリが必要です/必要です。ルビーの回避策はありません(いくつかのクエリを使用)。

回答:


125

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です)


6
条件がINでない場合に同じことを行う方法は?
coorasse 2013

13
@coorasse:Rails 4を使用している場合、not条件があります。この投稿のアプローチを調整することで、Rails 3でそれを実現できました。 subquery = Profile.select("user_id").where(gender: 'm')).to_sql; Message.where('user_id NOT IN (#{subquery})) 基本的に、ActiveRecordメソッドを使用して、完成した適切に引用されたサブクエリを作成し、それを外部クエリにインライン化します。主な欠点は、サブクエリパラメータがバインドされていないことです。
1217 2013

3
Rails4に関する@ twelve17のポイントを終了するために、特定のnot構文はMessage.where.not(user_id: Profile.select("user_id").where(gender: 'm'))-「NOTIN 」副選択を生成します。ジャストは..私の問題を解決
スティーブ・ミジリー

1
@ChristopherLindblom Railsがデフォルトでこれを「今」行うと言うとき、正確にはどういう意味ですか?Rails 3.2の時点で?答えを「RailsはバージョンXの時点でデフォルトでこれを行う」と言うように変更できればいいのですが。
Jason Swett 2017

@JasonSwett申し訳ありませんが、わかりません。現在のバージョンであり、リリースされたバージョンのみを実行したため、おそらく3.2でした。今後の保証について考えていただきますので、ご指摘いただきありがとうございます。
クリストファーリンドブロム2018年

43

ARelでは、where()メソッドは「WHERE idIN ...」クエリを生成する引数として配列を受け取ることができます。だからあなたが書いたものは正しい線に沿っています。

たとえば、次のARelコード:

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

...これは次と同等です:

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

... PostgreSQLデータベースに次のSQLを出力します。

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

更新:コメントに応じて

さて、私はその質問を誤解しました。2つのクエリでデータベースにアクセスしないようにするために、サブクエリに選択する列名を明示的にリストする必要があると思います(これは最も単純なケースで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)

今回はあなたが欲しいものをお渡ししたことを心から願っています!


はい。ただし、これは、1つのサブクエリを含む単一のクエリではなく、2つの個別のクエリを生成するため、私が望んでいないことです。
gucki 2011

あなたの質問を誤解してしまったことをお詫びします。SQLをどのように見せたいかの例を挙げてください。
スコット

問題ない。すでに上で述べました:SELECT * FROMユーザー
WHEREid

1
あ、そう。私はあなたが今言っていることを理解します。2つのクエリが生成されるという意味がわかります。幸いなことに、私はあなたの問題を解決する方法を知っています!(改訂された回答を参照)
スコット

23

私はこの質問に対する答えを自分で探していましたが、別のアプローチを考え出しました。私はそれを共有したいと思っていました-それが誰かを助けることを願っています!:)

# 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})")

ビンゴ!バンゴ!

Rails3.1で動作します


4
最初のクエリを2回実行します。それを行う方が良い subquery = Account.where(...).select(:id).to_sql query = User.where("users.account_id IN (#{subquery})")
coorasse 2013

9
クエリでto_sを呼び出して表示するため、REPLで最初のクエリを2回だけ実行します。アプリケーションで一度だけ実行されます。
リッチー

アカウントテーブルから複数の列が必要な場合はどうなりますか?
アフマドハムザ2018年

0

別の選択肢:

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

0

これは、rails ActiveRecordとJOINを使用したネストされたサブクエリの例であり、各クエリと結果に句を追加できます。

ネストされたinner_queryスコープとouter_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("...")
   }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.