Arel中的嵌套查询

我试图在Arel和/或Rails 3中的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)中的子查询来完成上述操作? 也许有一种完全不同的方式来完成这个不使用嵌套查询的查询?

这是我对临时表和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等做一些不错的事情……

inner_query – >

 select * from your_models where stuff='foo' 

outer_query – >

 select * from (select * from your_models where stuff='foo') as results; 

问题是为什么你需要一个“嵌套查询”? 我们不需要使用“嵌套查询”这是在思考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和时间戳的投影非常重要,因为我们无法从关系中投射所有域(即排序。*)。 您必须专门预测将在关系的分组操作中使用的所有域。 原因是没有*的值可以清楚地代表分组的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。

虽然我不认为这个问题需要嵌套查询,比如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)))) # 
 Point. from(Point.order(Point.arel_table[:timestamp].desc).as("sorted")). select("sorted.*"). group("sorted.client_id") 

要在“纯粹的”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 

当然,在您的情况下,点数和分类将从点模型中检索和定制,而不是如上制造。