12

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

在 SQL 中,我们可以通过以下方式做到这一点

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

还有比这更好的实现吗?

4

6 回答 6

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

有关更多信息,请参阅http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-minimum

于 2008-10-09T01:55:01.760 回答
2

这对我有用。

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

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

{
 "type1"=>500.0,
 "type2"=>200.0
}
于 2021-02-25T01:05:42.963 回答
1

您可以使用 # 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 做得更好时,你会使用它。

于 2008-10-09T07:06:57.947 回答
1

我已经为此奋斗了一段时间,目前看来你几乎被困在生成 SQL 上。

但是,我有一些改进可以提供。

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

于 2013-02-14T18:45:26.333 回答
0

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

于 2020-04-28T18:31:24.620 回答
-1

要更新上面 Avdi 的答案:

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

这是更新后的网址:

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

于 2014-06-10T08:03:18.460 回答