【问题标题】:Subqueries in activerecordactiverecord 中的子查询
【发布时间】:2011-07-25 21:36:24
【问题描述】:

使用 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 来做到这一点?

我确实需要/想要真正的子查询,没有 ruby​​ 解决方法(使用多个查询)。

【问题讨论】:

    标签: ruby-on-rails-3.1 subquery arel


    【解决方案1】:

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

    更新:响应 cmets

    好的,所以我误解了这个问题。我相信您希望子查询显式列出要选择的列名,以免通过两个查询访问数据库(这是 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)
    

    我真诚地希望这次我给了你你想要的!

    【讨论】:

    • 是的,但这正是我想要的,因为它会生成两个单独的查询,而不是包含一个子查询的单个查询。
    • 对您的问题的误解表示歉意。你能举个例子说明你希望你的 SQL 是什么样子吗?
    • 没问题。上面已经提到过:SELECT * FROM users WHERE id IN (SELECT user_id FROM accounts WHERE ..)
    • 啊,好吧。我明白你现在在说什么。我明白您对生成 2 个查询的意思。幸运的是,我知道如何解决您的问题! (见修改后的答案)
    【解决方案2】:

    我自己也在寻找这个问题的答案,我想出了另一种方法。我只是想我会分享它-希望它对某人有所帮助! :)

    # 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

    【讨论】:

    • 它执行第一个查询两次。最好做subquery = Account.where(...).select(:id).to_sqlquery = User.where("users.account_id IN (#{subquery})")
    • 它只会在你的 REPL 中执行第一个查询两次,因为它在查询上调用 to_s 来显示它。它只会在您的应用程序中执行一次。
    • 如果我们想要帐户表中的多个列怎么办?
    【解决方案3】:

    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)

    【讨论】:

    • 如果条件不是 IN 怎么办?
    • @coorasse:如果您使用的是 Rails 4,现在有一个 not condition。我可以通过调整this post 中的方法在Rails 3 中完成它:subquery = Profile.select("user_id").where(gender: 'm')).to_sql; Message.where('user_id NOT IN (#{subquery})) 基本上,ActiveRecord 方法用于创建完整的、正确引用的子查询,然后将其内联到外部查询中。主要缺点是子查询参数没有绑定。
    • 为了完成@twelve17 关于Rails 4 的观点,具体的not 语法是Message.where.not(user_id: Profile.select("user_id").where(gender: 'm')) - 生成一个“NOT IN”子选择。刚刚解决了我的问题..
    • @ChristopherLindblom 当您说 Rails “现在”默认执行此操作时,您究竟是什么意思?从 Rails 3.2 开始?如果我们可以将答案更改为“Rails 从版本 X 开始默认执行此操作”,那就太好了。
    • @JasonSwett 对不起,我不知道,正如你所说,它可能是 3.2,因为它是当前版本,并且只运行发布的版本。会考虑未来的校对答案,感谢您指出这一点。
    【解决方案4】:

    另一种选择:

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

    【讨论】:

      【解决方案5】:

      这是一个使用 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("...")
         }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-04-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-09-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多