我的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编写它?
答案 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它只是从整个表中拉出来而没有过滤。