获取 ActiveRecord 中每个组的最小值/最大值
Posted
技术标签:
【中文标题】获取 ActiveRecord 中每个组的最小值/最大值【英文标题】:Fetching Minimum/Maximum for each group in ActiveRecord 【发布时间】:2010-09-16 04:20:10 【问题描述】:这是一个古老的问题,给定一个具有“类型”、“品种”和“价格”属性的表格,您可以获取每种类型的最低价格记录。
在 SQL 中,我们可以通过:this:
select f.type, f.variety, f.price
from ( select type, min(price) as minprice from table group by type ) as x
inner join table as f on f.type = x.type and f.price = x.minprice;`
我们或许可以通过以下方式模仿:
minprices = Table.minimum(:price, :group => type)
result = []
minprices.each_pair do |t, p|
result << Table.find(:first, :conditions => ["type = ? and price = ?", t, p])
end
还有比这更好的实现吗?
【问题讨论】:
如何获得每种类型的最高和最低价格?? 【参考方案1】:这对我有用。
Table.group(:type).minimum(:price)
它返回一个像这样的对象。
"type1"=>500.0,
"type2"=>200.0
【讨论】:
【参考方案2】:虽然这个问题很陈旧,但我今天问的是同样的问题。 下面是一个解决方案的要点,它可以用最少 (2) 个查询来组合实现目标所需的 SQL。
如果这几天有更好的方法,请lmk!
使用 Security
和 Price
模型,其中证券有许多(历史)价格,并且您在寻找证券的最新价格:
module MostRecentBy
def self.included(klass)
klass.scope :most_recent_by, ->(group_by_col, max_by_col)
from(
<<~SQL
(
SELECT #table_name.*
FROM #table_name JOIN (
SELECT #group_by_col, MAX(#max_by_col) AS #max_by_col
FROM #table_name
GROUP BY #group_by_col
) latest
ON #table_name.date = latest.#max_by_col
AND #table_name.#group_by_col = latest.#group_by_col
) #table_name
SQL
)
end
end
class Price < ActiveRecord::Base
include MostRecentBy
belongs_to :security
scope :most_recent_by_security, -> most_recent_by(:security_id, :date)
end
class Security < ActiveRecord::Base
has_many :prices
has_one :latest_price,
-> Price.most_recent_by_security ,
class_name: 'Price'
end
现在您可以在控制器代码中调用以下代码:
def index
@resources = Security.all.includes(:latest_price)
render json: @resources.as_json(include: :latest_price)
end
导致两个查询:
Security Load (4.4ms) SELECT "securities".* FROM "securities"
Price Load (140.3ms) SELECT "prices".* FROM (
SELECT prices.*
FROM prices JOIN (
SELECT security_id, MAX(date) AS date
FROM prices
GROUP BY security_id
) latest
ON prices.date = latest.date
AND prices.security_id = latest.security_id
) prices
WHERE "prices"."price_type" = $1 AND "prices"."security_id" IN (...)
供参考:https://gist.github.com/pmn4/eb58b036cc78fb41a36c56bcd6189d68
【讨论】:
【参考方案3】:Table.minimum(:price, :group => :type)
请参阅http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-minimum 了解更多信息。
【讨论】:
如何获得每种类型的最高和最低价格?? 出于某种原因,:group => :type
部分被忽略了,对我来说。【参考方案4】:
要更新上面 Avdi 的答案:
Table.minimum(:price, :group => :type)
这是更新后的网址:
http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-minimum
【讨论】:
也许链接到更新版本的 Rails 会有所帮助。不知道您为什么选择链接到 2 个主要版本。 谢谢艾萨克。已更新。【参考方案5】:我一直在努力解决这个问题,目前看来您几乎无法生成 SQL。
但是,我有一些改进可以提供。
正如@François 建议的那样,我使用ActiveRecord 的to_sql
和joins
来“引导”我的SQL,而不是find_by_sql
:
subquery_sql = Table.select(["MIN(price) as price", :type]).group(:type).to_sql
joins_sql = "INNER JOIN (#subquery_sql) as S
ON table.type = S.type
AND table.price = S.price"
Table.joins(joins_sql).where(<other conditions>).order(<your order>)
如您所见,我仍在使用原始 SQL,但至少它只是在 AR 不提供支持的部分(AFAIK ActiveRecord 根本无法管理 INNER JOIN ... ON ...
),而不是整个事情。
使用joins
代替 find_by_sql 使查询可链接 - 您可以添加额外的条件,或对表进行排序,或将所有内容放在一个范围内。
【讨论】:
【参考方案6】:您可以使用#find_by_sql
,但这意味着返回一个模型对象,这可能不是您想要的。
如果你想裸奔金属,也可以使用#select_values
:
data = ActiveRecord::Base.connection.select_values("
SELECT f.type, f.variety, f.price
FROM (SELECT type, MIN(price) AS minprice FROM table GROUP BY type ) AS x
INNER JOIN table AS f ON f.type = x.type AND f.price = x.minprice")
puts data.inspect
[["type", "variety", 0.00]]
ActiveRecord 只是一个工具。你在方便的时候使用它。当 SQL 做得更好时,你就会使用它。
【讨论】:
以上是关于获取 ActiveRecord 中每个组的最小值/最大值的主要内容,如果未能解决你的问题,请参考以下文章