Skip to content

Instantly share code, notes, and snippets.

@pmn4
Last active April 28, 2020 18:28
Show Gist options
  • Save pmn4/eb58b036cc78fb41a36c56bcd6189d68 to your computer and use it in GitHub Desktop.
Save pmn4/eb58b036cc78fb41a36c56bcd6189d68 to your computer and use it in GitHub Desktop.
ActiveRecord: Max By with Group
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
Security.all.includes(:latest_price).to_json(include: :latest_price)
# or
Security.where(selection: :criteria).includes(:latest_price)
.to_json(include: :latest_price)
@pmn4
Copy link
Author

pmn4 commented Apr 28, 2020

The goal is to eagerly load the price with the most recent date for each security in a set with as few queries as possible (two, ideally).
Price.most_recent_by does the work of composing a query to accomplish that.
The result:

Security.where(id: [1, 2, 3]).includes(:latest_price).each { |s| puts "#{s.ticker} -> #{s.latest_price.try(:price)}" }
  Security Load (2.1ms)  SELECT "securities".* FROM "securities" WHERE "securities"."id" IN ($1, $2, $3)
  Price Load (33.4ms)  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 ($2, $3, $4)

A -> 6.25 (2020-04-17)
B -> 7.44 (2020-04-20)
C -> 58.4 (2020-04-20)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment