获取 ActiveRecord 中每个组的最小值/最大值

发布于 2024-07-06 18:09:13 字数 687 浏览 16 评论 0原文

这是一个古老的问题,给定一个具有“类型”、“品种”和“价格”属性的表,您可以获取每种类型的最低价格记录。

在 SQL 中,我们可以执行 this by:

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

是否有比这更好的实现?

This is an age-old question where given a table with attributes 'type', 'variety' and 'price', that you fetch the record with the minimum price for each type there is.

In SQL, we can do this by:

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;`

We could perhaps imitate this by:

minprices = Table.minimum(:price, :group => type)  
result = []
minprices.each_pair do |t, p|  
   result << Table.find(:first, :conditions => ["type = ? and price = ?", t, p])
end

Is there a better implementation than this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

辞取 2024-07-13 18:09:13
Table.minimum(:price, :group => :type)

请参阅 http://api.rubyonrails.org/classes/ActiveRecord /Calculations.html#method-i-minimum 了解更多。

同展鸳鸯锦 2024-07-13 18:09:13

这对我有用。

Table.group(:type).minimum(:price)

它返回一个像这样的对象。

{
 "type1"=>500.0,
 "type2"=>200.0
}

This worked for me.

Table.group(:type).minimum(:price)

And it returns an object like this.

{
 "type1"=>500.0,
 "type2"=>200.0
}
鲜肉鲜肉永远不皱 2024-07-13 18:09:13

您可以使用#find_by_sql,但是这意味着返回一个模型对象,这可能不是您想要的。

如果你想直接尝试,也可以使用 #< code>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 做得更好时,您就可以使用它。

You can use #find_by_sql, but this implies returning a model object, which might not be what you want.

If you want to go bare to the metal, you can also use #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 is just a tool. You use it when it's convenient. When SQL does a better job, you use that.

一城柳絮吹成雪 2024-07-13 18:09:13

我已经为此奋斗了一段时间,目前看来您几乎陷入了生成 SQL 的困境。

然而,我有一些改进要提供。

@François 建议的那样,我没有使用 find_by_sql ,而是使用 ActiveRecord 的 to_sqljoins 来“指导”我的 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使查询可链接 - 您可以添加额外的条件,或对表进行排序,或将所有内容放在一个范围内。

I've been fighting with this for a while and for the moment it seems that you are pretty much stuck with generating SQL.

However, I have a couple refinements to offer.

Instead of find_by_sql, as @François suggested, I've used ActiveRecord's to_sql and joins to "guide" my SQL a little bit:

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

As you can see, I'm still using raw SQL, but at least it's only on the part where AR gives no support (AFAIK ActiveRecord simply can't manage INNER JOIN ... ON ...) and not on the whole thing.

Using joins instead of find_by_sql makes the query chainable - you can add extra conditions, or sort the table, or put everything in a scope.

山人契 2024-07-13 18:09:13

虽然这个问题很陈旧,但我今天问了同样的问题。
下面是一个解决方案的要点,该解决方案使用最少 (2) 次查询来编写实现目标所需的 SQL。

请lmk这些天是否有更好的方法!

使用SecurityPrice模型,其中证券有许多(历史)价格,并且您正在寻找证券的最新价格:

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< /a>

While this question is pretty stale, I was asking the same question today.
Here's a gist of a solution to compose the SQL needed to accomplish the goal with minimal (2) queries.

Please lmk if there are better ways these days!

Using Security and Price models, where Securities have many (historical) Prices, and you are after the Securities' most recent 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

now you can call the following in your controller code:

def index
  @resources = Security.all.includes(:latest_price)

  render json: @resources.as_json(include: :latest_price)
end

which results in two queries:

  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 (...)

for reference: https://gist.github.com/pmn4/eb58b036cc78fb41a36c56bcd6189d68

一城柳絮吹成雪 2024-07-13 18:09:13

更新上面 Avdi 的答案:

Table.minimum(:价格, :group => :type)

这是更新后的 URL:

http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-minimum

To update Avdi's answer above:

Table.minimum(:price, :group => :type)

Here is the updated URL:

http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-minimum

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文