如何使用SQL按年获取最新的值组

时间:2016-12-27 12:44:02

标签: ruby-on-rails postgresql

我的Company模型has_many Statement

class Company < ActiveRecord::Base
  has_many :statements
end

我希望得到statements字段按date字段分组的最新fiscal_year_end字段。

我实现了这样的功能:

c = Company.first
c.statements.to_a.group_by{|s| s.fiscal_year_end }.map{|k,v| v.max_by(&:date) }

它工作正常,但如果可能的话我想使用ActiveRecord查询(SQL),这样我就不需要将不必要的实例加载到内存中。

如何使用SQL编写它?

4 个答案:

答案 0 :(得分:2)

select t.username, t.date, t.value
from MyTable t
inner join (
    select username, max(date) as MaxDate
    from MyTable
    group by username
) tm on t.username = tm.username and t.date = tm.MaxDate

答案 1 :(得分:2)

对于这些类型的东西,我发现首先使原始SQL工作,然后将其转换为ActiveRecord很有帮助。这听起来像是GROUP BY

的教科书案例
SELECT  fiscal_year_end, MAX(date) AS max_date
FROM    statements
WHERE   company_id = 1
GROUP BY fiscal_year_end

现在您可以像在ActiveRecord中那样表达:

c = Company.first
c.statements.
  group(:fiscal_year_end).
  order(nil).   # might not be necessary, depending on your association and Rails version
  select("fiscal_year_end, MAX(date) AS max_date")

order(nil)的原因是阻止ActiveRecord将ORDER BY id添加到查询中。 Rails 4+会自动执行此操作。由于您未按id进行分组,因此会导致您看到的错误。如果那是你想要的,你也可以order(:fiscal_year_end)

这会给你一堆Statement个对象。它们是只读的,除了nil和神奇的新字段fiscal_year_end之外,每个属性都是max_date。这些实例并不代表具体的陈述,但声明&#34; group&#34;来自您的查询。所以你可以这样做:

- @statements_by_fiscal_year_end.each do |s|
  %tr
    %td= s.fiscal_year_end
    %td= s.max_date

请注意,此处没有n + 1查询问题,因为您在一个查询中提取了所需的所有内容。

如果您认为自己需要的不仅仅是最长日期,例如如果您希望整个语句包含最新日期,那么您应该查看greatest n per group problem的选项。对于原始SQL,我喜欢LATERAL JOIN,但与ActiveRecord一起使用的最简单方法是DISTINCT ON

哦,还有一个提示:为了调试奇怪的错误,我发现确认SQL ActiveRecord正在尝试使用它是有帮助的。您可以使用to_sql来获取该信息:

c = Company.first
puts c.statements.
  group(:fiscal_year_end).
  select("fiscal_year_end, MAX(date) AS max_date").
  to_sql

在该示例中,我离开了order(nil),因此您可以看到ActiveRecord正在添加您不想要的ORDER BY条款。

答案 2 :(得分:0)

例如,您希望在应该使用此

的月份之前获取所有语句
@companey = Company.first
@statements = @companey.statements.find(:all, :order => 'due_at, id', :limit => 50)

然后根据需要对它们进行分组

@monthly_statements = @statements.group_by { |statement| t.due_at.beginning_of_month }

答案 3 :(得分:0)

根据Bharat的回答,您可以使用find_by_sql以这种方式在Rails中执行此类查询:

Statement.find_by_sql ["Select t.* from statements t INNER JOIN (
  SELECT fiscal_year_end, max(date) as MaxDate GROUP BY fiscal_year_end
  ) tm on t.fiscal_year_end = tm.fiscal_year_end AND
  t.created_at = tm.MaxDate WHERE t.company_id = ?", company.id]

注意最后一个部分,以确保语句属于特定的公司实例,并且从类中调用它。我还没有使用数组形式对此进行测试,但我相信您可以将其转换为范围并使用它:

# In Statement model
scope :latest_from_fiscal_year, lambda |enterprise_id| {
    find_by_sql[..., enterprise_id] # Query above
}

# Wherever you need these statements for a particular company
company = Company.find(params[:id])
latest_statements = Statement.latest_from_fiscal_year(company.id)

请注意,如果您以某种方式需要所有公司的所有最新声明,那么这很可能会给您带来N + 1查询问题。但这是另一天的野兽。

注意:如果其他人有办法让这个查询在关联上工作而不使用最后一个部分(company.statements.latest_from_year等)让我知道并且我将编辑它,在我的情况下rails 3它只是从整个表中拉出来而没有过滤。