【问题标题】:Fetching Minimum/Maximum for each group in ActiveRecord获取 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

还有比这更好的实现吗?

【问题讨论】:

  • 如何获得每种类型的最高和最低价格??

标签: activerecord group-by aggregate-functions minimum


【解决方案1】:

这对我有用。

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

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

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

【讨论】:

    【解决方案2】:

    虽然这个问题很陈旧,但我今天问的是同样的问题。 下面是一个解决方案的要点,它可以用最少 (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

    【讨论】:

      【解决方案3】:
      Table.minimum(:price, :group => :type)
      

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

      【讨论】:

      • 如何获得每种类型的最高和最低价格??
      • 出于某种原因,:group =&gt; :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_sqljoins 来“引导”我的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 做得更好时,你就会使用它。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-04-28
          • 1970-01-01
          • 2010-11-23
          • 2011-08-16
          相关资源
          最近更新 更多