Получение минимума / максимума для каждой группы в ActiveRecord

Это давний вопрос, когда из таблицы с атрибутами «тип», «разнообразие» и «цена» вы получаете запись с минимальной ценой для каждого типа.

В 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

Есть ли лучшая реализация, чем эта?

как получить максимальную и минимальную цену для каждого вида ??

aashish 18.08.2016 19:38
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
11
1
10 772
6

Ответы 6

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

Подробнее см. http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-minimum.

как получить максимальную и минимальную цену для каждого вида ??

aashish 18.08.2016 19:37

По какой-то причине часть :group => :type для меня игнорируется.

EliadL 11.04.2019 19:27

Вы можете использовать # 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 работает лучше, вы его используете.

Я боролся с этим некоторое время, и на данный момент кажется, что вы в значительной степени застряли с генерацией SQL.

Тем не менее, я могу предложить несколько уточнений.

Вместо find_by_sql, как предложил @ François, я использовал ActiveRecord to_sql и joins, чтобы немного "направить" мой 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 делает запрос цепочкой - вы можете добавить дополнительные условия, отсортировать таблицу или поместить все в область видимости.

Чтобы обновить ответ Авди выше:

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

Вот обновленный URL:

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

Возможно, вам будет полезна ссылка на более свежую версию Rails. Не уверен, почему вы выбрали ссылку на две основные версии назад.

Isaac Betesh 08.07.2014 22:50

Спасибо, Исаак. Обновлено.

danielmbarlow 06.05.2015 10:50

Хотя этот вопрос довольно устаревший, я задавал тот же вопрос сегодня. Вот суть решения для составления SQL, необходимого для достижения цели с помощью минимальных (2) запросов.

Пожалуйста, посмотрите, есть ли в наши дни способы получше!

Используя модели 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" =  AND "prices"."security_id" IN (...)

для справки: https://gist.github.com/pmn4/eb58b036cc78fb41a36c56bcd6189d68

Это сработало для меня.

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

И он возвращает такой объект.

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

Другие вопросы по теме