Arel 中的嵌套查询
Posted
技术标签:
【中文标题】Arel 中的嵌套查询【英文标题】:Nested queries in Arel 【发布时间】:2011-02-23 02:25:24 【问题描述】:我正在尝试在 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)中的子查询来完成上述操作?也许有一种完全不同的方式来完成这个不使用嵌套查询的查询?
【问题讨论】:
【参考方案1】:这是我处理临时表和 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、分页、选择、分组等做一些不错的事情......
inner_query ->
select * from your_models where stuff='foo'
outer_query ->
select * from (select * from your_models where stuff='foo') as results;
【讨论】:
你也可以得到一个outer_query,而不必指定一个假的模型或表名。上面的最后两行可以用这一行替换,这就是“from”无论如何调用的内容:outer_query = Arel::SelectManager.new(Arel::Table.engine, Arel.sql("(#inner_query. to_sql) 作为结果"))【参考方案2】:问题是为什么需要“嵌套查询”? 我们不需要使用“嵌套查询”,这是在 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 以来,您已经开始知道如何操作了。
【讨论】:
感谢您的详细回复。 “时间戳顺序无关紧要,因为没有单个时间戳可以代表一个分组。”你说得对;我明白你在说什么。似乎 mysql 通过仅返回 client_id 组的第一行来解决这种不一致问题,这正是我的目标。我现在看到这不是我应该指望的行为。我的目标是返回所有 client_id 的最新点,即每个 client_id 分组具有最大时间戳的单个点。一次查询很重要,因为它会经常被轮询。 我们需要使用一些聚合函数。如果我们问自己“我们想要做什么?”答案是找到最近的或“最大”的日期,这样我们就可以在 sql 中传递 max(timestamp)。这将对应于 Arel::Attribute::Expression::Maximum ,可以使用 Arel::Attribute 上的语法糖调用 sorted[:timestamp].maximum() 。有一个警告。确保将时间戳添加到组操作 #group('client_id, timestamp') 或整个分组场景将出错。我知道 MAX 聚合函数适用于 Postgres 中的日期,我确信在 MySQL 中也是如此。 首先,排序和排序不是关系代数的一部分。无论如何,Arel 定义了它。其次,子查询是否是关系代数的一部分是无关紧要的。从概念上讲,SELECT 的结果在 WHERE 子句执行之前是不可见的。因此,并非所有数据库(例如 Postgres)都允许在 WHERE 子句中使用列别名,而是依赖于子查询。如果 Arel 无法处理子查询,则 WHERE 子句中的名称不能被别名。当您不能依赖 Arel 生成名称时,这可能会变得一团糟。 @SamuelDanielson 据我所知,Postgres 允许在 WHERE 子句中使用列别名。我知道的所有其他 SQL 数据库也是如此。 @PinnyM 好吧,我每天都学到新东西!我以为我在WHERE
中使用了别名,但显然我只在JOIN
中使用了它们。这导致我来到 ***.com/a/942592/109011 ,我发现这似乎是一个 SQL 限制。进一步的测试证实,只有 SQLite 支持 WHERE
子句中的列别名。但是,您的原始示例还有一个问题:sum
是一个聚合函数,并且在运行 WHERE
子句时不会计算聚合。所以我把sqlfiddle.com/#!12/86136/4放在一起,更清楚地说明了这一点。【参考方案3】:
虽然我不认为这个问题需要嵌套查询,就像 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))))
#
【讨论】:
小修正,而不是使用Arel::SqlLiteral
,正确的是Arel::Nodes::SqlLiteral
【参考方案4】:
Point.
from(Point.order(Point.arel_table[:timestamp].desc).as("sorted")).
select("sorted.*").
group("sorted.client_id")
【讨论】:
【参考方案5】:要在“纯”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
当然,在您的情况下,点和排序将从点模型中检索和定制,而不是像上面那样制造。
【讨论】:
以上是关于Arel 中的嵌套查询的主要内容,如果未能解决你的问题,请参考以下文章