19

我试图在 Rails 3 中的 Arel 和/或 Active Record 中嵌套 SELECT 查询以生成以下 SQL 语句。

SELECT sorted.* FROM (SELECT * FROM points ORDER BY points.timestamp DESC) AS sorted GROUP BY sorted.client_id

可以通过执行创建子查询的别名

points = Table(:points)
sorted = points.order('timestamp DESC').alias

但后来我被困在如何将它传递给父查询(没有调用#to_sql,这听起来很丑陋)。

您如何使用 SELECT 语句作为 Arel(或 Active Record)中的子查询来完成上述操作?也许有一种完全不同的方式来完成这个不使用嵌套查询的查询?

4

5 回答 5

25

这是我处理临时表和 Arel 的方法。它使用 Arel#from 方法通过 Arel#to_sql 传入内部查询。

inner_query = YourModel.where(:stuff => "foo")
outer_query = YourModel.scoped  # cheating, need an ActiveRelation
outer_query = outer_query.from(Arel.sql("(#{inner_query.to_sql}) as results")).
                          select("*")

现在你可以用 outer_query、paginate、select、group 等做一些好事……

内部查询->

select * from your_models where stuff='foo'

外部查询->

select * from (select * from your_models where stuff='foo') as results;
于 2011-10-12T04:51:05.917 回答
8

问题是为什么需要“嵌套查询”?我们不需要使用“嵌套查询”,这是在 SQL 而不是关系代数的思维方式中思考的。使用关系代数,我们推导出关系并将一个关系的输出用作另一个关系的输入,因此以下情况成立:

points = Table(:points, {:as => 'sorted'}) # rename in the options hash
final_points = points.order('timestamp DESC').group(:client_id, :timestamp).project(:client_id, :timestamp)

除非绝对必要,否则最好将重命名保留为 arel。

在这里,client_id 和时间戳的投影非常重要,因为我们不能从关系中投影所有域(即 sorted.*)。您必须专门规划将在关系的分组操作中使用的所有域。原因是 * 没有明确代表分组 client_id 的值。例如说你有下表

client_id   |   score
----------------------
    4       |    27
    3       |    35
    2       |    22
    4       |    69

在这里,如果您分组,则无法在分数域上执行投影,因为该值可能是 27 或 69,但您可以投影总和(分数)

您只能将具有唯一值的域属性投影到组(通常是聚合函数,如 sum、max、min)。对于您的查询,这些点是否按时间戳排序并不重要,因为最终它们将按 client_id 分组。时间戳顺序无关紧要,因为没有单个时间戳可以代表一个分组。

请让我知道如何帮助您处理 Arel。此外,我一直在制作一个学习系列,供人们使用 Arel 作为其核心。该系列的第一篇在http://Innovative-Studios.com/#pilot 我可以告诉你,因为你使用 Table(:points) 而不是 ActiveRecord 模型 Point,所以你开始知道如何使用了。

于 2010-05-24T13:02:32.050 回答
7

虽然我不认为这个问题需要嵌套查询,就像 Snuggs 提到的那样。对于那些确实需要嵌套查询的人。这是我到目前为止的工作,不是很好,但它有效:

class App < ActiveRecord::Base   
  has_many :downloads

  def self.not_owned_by_users(user_ids)
    where(arel_table[:id].not_in( 
      Arel::SqlLiteral.new( Download.from_users(user_ids).select(:app_id).to_sql ) ) )
  end
end

class Download  < ActiveRecord::Base
  belongs_to :app
  belongs_to :user

  def self.from_users(user_ids)
    where( arel_table[:user_id].in user_ids )
  end

end

class User < ActiveRecord::Base
  has_many :downloads
end

App.not_owned_by_users([1,2,3]).to_sql #=>
# SELECT `apps`.* FROM `apps` 
# WHERE (`apps`.`id` NOT IN (
#   SELECT app_id FROM `downloads` WHERE (`downloads`.`user_id` IN (1, 2, 3))))
#
于 2010-12-30T13:06:20.337 回答
7
Point.
 from(Point.order(Point.arel_table[:timestamp].desc).as("sorted")).
 select("sorted.*").
 group("sorted.client_id")
于 2012-07-20T03:29:28.717 回答
1

要在“纯” Arel 中执行此操作,这对我有用:

points = Arel::Table.new('points')
sorted = Arel::Table.new('points', as: 'sorted')
query = sorted.from(points.order('timestamp desc').project('*')).project(sorted[Arel.star]).group(sorted[:client_id])
query.to_sql

当然,在您的情况下,点和排序将从点模型中检索和定制,而不是像上面那样制造。

于 2014-08-22T23:45:35.673 回答