没有特定子记录的父记录的Rails范围

时间:2017-12-14 03:44:02

标签: sql ruby-on-rails ruby postgresql

我有一个父模型Effort has_many split_times:

class Effort
  has_many :split_times
end

class SplitTime
  belongs_to :effort
  belongs_to :split
end

class Split
  has_many :split_times
  enum kind: [:start, :finish, :intermediate]
end

我需要一个范围来返回没有启动split_time的工作。这似乎应该是可能的,但到目前为止我无法做到。

我可以通过以下方式返回没有split_times的工作:

scope :without_split_times, -> { includes(:split_times).where(split_times: {:id => nil}) }

我可以返回至少有一次split_time的努力:

scope :with_split_times, -> { joins(:split_times).uniq }

这是我对我想要的范围的尝试:

scope :without_start_time, -> { joins(split_times: :split).where(split_times: {:id => nil}).where('splits.kind != ?', Split.kinds[:start]) }

但这不起作用。我需要一些能够返回没有split_time的所有努力的东西:即使努力​​有其他split_times,也要开始。我更喜欢Rails解决方案,但如果需要可以转到原始SQL。如果重要的话,我正在使用Postgres。

1 个答案:

答案 0 :(得分:1)

您可以在您的条件(即splits.kind = 'start')上保持联接,其中包含空值(即没有匹配的行可以加入)。不同之处在于Rails'默认情况下,join将为您提供内部联接(两个表中都有匹配的行),但您需要左联接,因为您需要检查右表中是否没有匹配的行。

使用该连接的结果,您可以按事件分组,然后计算匹配拆分的数量 - 如果它是0,那么该事件没有匹配的开始拆分!

这可能适合你:

scope :without_start_time, -> { 
  joins("LEFT JOIN split_times ON split_times.effort_id = efforts.id").
    joins("LEFT OUTER JOIN splits ON split_times.split_id = splits.id " \
          "AND splits.kind = 0").
    group("efforts.id").
    having("COUNT(splits.id) = 0")
}