ActiveRecord中的子查询


82

使用SQL,我可以轻松地执行这样的子查询

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

这将产生:

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

如何使用Rails的3 activerecord / arel / meta_where做到这一点?

我确实需要/想要真正的子查询,没有红宝石的解决方法(使用几个查询)。

Answers:


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')

(“ now”所指的版本号很可能是3.2)


6
如果条件不在,该怎么做?
coorasse

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方法用于创建完整的,带正确引号的子查询,然后将其内联到外部查询中。主要缺点是未绑定子查询参数。
13年

3
为了完成@ twelve17关于Rails 4的观点,特定的非语法是Message.where.not(user_id: Profile.select("user_id").where(gender: 'm'))-生成“ NOT IN”子选择。刚解决了我的问题
。–史蒂夫·米德利

1
@ChristopherLindblom当您说Rails“现在”默认情况下是这样做的时候,您的意思是什么?从Rails 3.2开始?如果我们可以将答案更改为“默认情况下,从X版本开始,Rails会这样做”,那就太好了。
Jason Swett

@JasonSwett对不起,我不知道,您所说的大概是3.2,因为它是时代的当前版本,只运行发行版本。会考虑将来的验证流程,谢谢您指出这一点。
Christopher Lindblom '18

43

在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

...将在PostgreSQL数据库上输出以下SQL:

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

更新:回应评论

好吧,所以我误解了这个问题。我相信您希望子查询显式列出要选择的列名,以便不通过两个查询命中数据库(这是ActiveRecord在最简单的情况下所做的事情)。

您可以使用projectselect您的子选择:

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)

衷心希望我这次能给您您想要的东西!


是的,但这正是我所希望的,因为它会生成两个单独的查询,而不是一个包含一个子查询的单个查询。
gucki 2011年

抱歉误解您的问题。您能否举例说明您希望SQL看起来像什么?
Scott

没问题。上面已经提到:SELECT * FROM用户的ID为IN(SELECT user_id的用户为FROM WHERE ..)
gucki 2011年

1
啊好吧。我明白你现在在说什么。我明白了您关于生成2个查询的意思。幸运的是,我知道如何解决您的问题!(请参阅修订后的答案)
Scott

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

答对了!班戈!

适用于Rails 3.1


4
它执行两次第一个查询。最好去做 subquery = Account.where(...).select(:id).to_sql query = User.where("users.account_id IN (#{subquery})")
coorasse

9
它只会在您的REPL中执行两次第一个查询,因为它在查询中调用to_s来显示它。它只会在您的应用程序中执行一次。
里奇

如果我们想要帐户表中有多列怎么办?
Ahmad hamza

0

另一种选择:

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

0

这是一个使用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("...")
   }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.