我有一个包含parent_id
和date
属性的事件模型:
Event.rb
has_many :children, :class_name => "Event"
belongs_to :parent, :class_name => "Event"
我在调用event.parent
或event.children
时没有任何问题。儿童事件本身从未有过孩子。
我正在尝试为此模型添加一个范围,以便我可以为每个父项返回具有最近未来日期的子项。类似的东西:
scope :future, -> {
where("date > ?", Date.today)
}
scope :closest, -> {
group('"parent_id"').having('date = MAX(date)')
}
Event.future.closest ==> returns the closest child event from every parent
但是上面的:closest
范围每个父母都会返回多个孩子。
答案 0 :(得分:1)
暂时忽略Rails,你在SQL中做的是greatest-n-per-group问题。这是lots of solutions。我会选择DISTINCT ON
或LEFT OUTER JOIN LATERAL
。以下是它在Rails中的外观:
scope :closest, -> {
select("DISTINCT ON (parent_id) events.*").
order("parent_id, date ASC")
}
这将为您提供子对象。 (您可能还希望条件排除没有parent_id
的行。)从您自己的解决方案中,听起来就像您想要的那样。如果您希望父对象,并使用可选的附加子对象,则使用横向连接。虽然转换成ActiveRecord有点麻烦。如果在两个查询中执行它是可以接受的,这看起来应该可以工作(坚持使用DISTINCT ON
):
has_one :closest_child, -> {
select("DISTINCT ON (parent_id) events.*").
order("parent_id, date ASC")
}, class_name: Event, foreign_key: "parent_id"
然后你可以说Event.includes(:closest_child)
。同样,您可能希望过滤掉所有非父母。
答案 1 :(得分:0)
我最终使用了:
scope :closest, -> {
where(id: Event.group(:parent_id).minimum(:date).keys)
}
答案 2 :(得分:0)
你自己的答案看起来不错,但我会按照以下方式改进它:
scope :closest, -> {
where.not(parent_id: nil).group(:parent_id).minimum(:date)
}
非常重要或者在生产中你总是将部署日期定为Date.today
,因为它只会在开发中重新加载:
scope :future, -> {
where("date > ?", Proc.new { Date.today })
}