Mysql 根据最近子元素的属性选择父元素

Mysql 根据最近子元素的属性选择父元素,mysql,ruby-on-rails,activerecord,greatest-n-per-group,Mysql,Ruby On Rails,Activerecord,Greatest N Per Group,我正在写一个简单的会员申请。我有两种型号,会员和会员 class Member < ActiveRecord::Base has_many :memberships end class Membership < ActiveRecord::Base belongs_to :member end 因为这将包括任何在过去拥有会员资格的现任会员。我真正需要做的是能够只加入到一个成员的最新成员资格中,并以此为基础进行查询。我是Rails的新手,虽然为此有些挣扎——非常感谢您的帮

我正在写一个简单的会员申请。我有两种型号,
会员
会员

class Member < ActiveRecord::Base
   has_many :memberships
end

class Membership < ActiveRecord::Base
  belongs_to :member
end
因为这将包括任何在过去拥有会员资格的现任会员。我真正需要做的是能够只加入到一个成员的最新成员资格中,并以此为基础进行查询。我是Rails的新手,虽然为此有些挣扎——非常感谢您的帮助或想法

编辑:显然,在普通的老SQL中,这是不难做到的事情,但我希望Rails有一些很好的方法来做到这一点(而不仅仅是粘贴SQL)


我也不希望为了实现我想要的查询而向这两个表中添加另一列。这太乱了,我不该这么做,而且很可能会造成问题。

让我们从逻辑上考虑一下

最近一次成员资格已过期的所有成员

实际上和

没有活动成员资格的所有成员

在后一种情况下,您可以在SQL中这样做

SELECT * FROM members
WHERE NOT EXISTS (
  SELECT * FROM memberships
  WHERE members.id = memberships.member_id
  AND   memberships.expires > #{Time.now}
)
使用活动记录也可以实现同样的效果

Member.where(["NOT EXISTS (
  SELECT * FROM memberships
  WHERE members.id = memberships.member_id
  AND   memberships.expires > ?
)", Time.now])
现在这是相当令人讨厌的,
但这正是你想要的。

让我们从逻辑上考虑一下

最近一次成员资格已过期的所有成员

实际上和

没有活动成员资格的所有成员

在后一种情况下,您可以在SQL中这样做

SELECT * FROM members
WHERE NOT EXISTS (
  SELECT * FROM memberships
  WHERE members.id = memberships.member_id
  AND   memberships.expires > #{Time.now}
)
使用活动记录也可以实现同样的效果

Member.where(["NOT EXISTS (
  SELECT * FROM memberships
  WHERE members.id = memberships.member_id
  AND   memberships.expires > ?
)", Time.now])
现在这是相当令人讨厌的,
但这正是您所要求的。

如果您在成员资格模型中添加过期列,并且在成员添加新成员资格时变为真,那么您可以轻松获得最后一个成员资格。

如果您在成员资格模型中添加过期列,并且在成员添加新成员资格时变为真,因此,您可以轻松获得最后的会员资格。

方法1-最大组数

通过使用
连接
,您可能会获得更好的结果

Member.joins("JOIN (
     SELECT a.member_id, MAX(a.expires) expires_at
     FROM   memberships a
     GROUP BY a.member_id
     WHERE a.paid = 1 AND  a.expires IS NOT NULL
   ) b ON b.member_id = members.id
  ").
  where("b.expires < ?", Time.now) 
现在,您可以通过以下方式获取最近过期的成员:

Member.recently_expired

方法1-组最大值

通过使用
连接
,您可能会获得更好的结果

Member.joins("JOIN (
     SELECT a.member_id, MAX(a.expires) expires_at
     FROM   memberships a
     GROUP BY a.member_id
     WHERE a.paid = 1 AND  a.expires IS NOT NULL
   ) b ON b.member_id = members.id
  ").
  where("b.expires < ?", Time.now) 
现在,您可以通过以下方式获取最近过期的成员:

Member.recently_expired

谢谢你的建议。我把它们投了赞成票,而不是接受它们,因为我觉得它们不能完全满足我的需要,但我将要做的事情肯定从中受益

我仍然认为,如果可能的话,应该避免使用SQL(出于我在评论中已经提到的所有原因),但我认为在这种情况下不能使用SQL,因此我决定为成员模型定义一个命名范围,如下所示:

#expired
Member.with_membership.find :all, :conditions => ['expires < ?', Time.now ]

#current
Member.with_membership.find :all, :conditions => ['started < ? AND expires > ?', Time.now, Time.now ]

#pending payment
Member.with_membership.find :all, :conditions => ['applied < ? AND paid IS NULL', Time.now ]
编辑:我最初将其定义为默认范围-但是我决定注意Kandaboggu关于复杂默认范围的警告,因此将其改为命名范围。当当前活动的成员身份存在时,为了处理排除续订的成员身份(开始日期在将来),该查询也比其他描述的查询要复杂一些。再次感谢Kandaboggu提供的查询要点和避免N+1选择的提示

scope :with_membership, lambda { 
select('members.*, m.applied AS applied, m.paid AS paid, m.start AS start, m.expiry as expiry').
joins("INNER JOIN (
  SELECT m3.*
  FROM memberships m3
  LEFT OUTER JOIN memberships m5 
  ON m3.member_id = m5.member_id 
  AND m5.created_at > m3.created_at
  AND m5.expiry > #{sanitize(Time.now)}
  WHERE m3.expiry > #{sanitize(Time.now)}
  AND m5.id IS NULL
  UNION
  SELECT m1.*
  FROM   memberships m1
  LEFT OUTER JOIN memberships m2 
  ON m1.member_id = m2.member_id 
  AND m2.created_at > m1.created_at 
  LEFT OUTER JOIN memberships m4 
  ON m1.member_id = m4.member_id 
  AND m4.expiry > #{sanitize(Time.now)}
  WHERE  m2.id IS NULL AND m4.id IS NULL
  ) m 
  on (m.member_id = members.id)") }         
我见过一些更漂亮的代码。但我的理由是——如果你必须有一个像这样可怕的SQL,你最好只在一个地方有它,而不是在每个不同的地方重复你可能需要的查询(如过期的成员,尚未付款的成员等)

有了上述范围,我可以将额外的成员资格列视为
Member
上的普通列,并编写如下非常简单的查询:

#expired
Member.with_membership.find :all, :conditions => ['expires < ?', Time.now ]

#current
Member.with_membership.find :all, :conditions => ['started < ? AND expires > ?', Time.now, Time.now ]

#pending payment
Member.with_membership.find :all, :conditions => ['applied < ? AND paid IS NULL', Time.now ]
#过期
Member.with_membership.find:all,:conditions=>['expires<?',Time.now]
#当前
Member.with_membership.find:all,:conditions=>['started<?AND expires>?',Time.now,Time.now]
#待付款
Member.with_membership.find:all,:conditions=>['applied<?AND paid IS NULL',Time.now]
为了简单地证明接受我自己的答案而不是其他非常有用的答案的合理性,我想指出,这从来不是一个关于如何获得“每组最大n”的问题(尽管这是其中的一个组成部分)。它是关于如何在Rails的特定环境中最好地处理这种查询,处理具有多个成员身份的成员的特定问题,其中在任何时候只有一个成员是活动的,以及处理许多类似的查询,所有这些查询都需要来自这个活动成员身份的字段


这就是为什么我认为以这种方式使用命名范围最终是这个问题的最佳答案。忍受可怕的SQL查询,但只在一个地方使用。

谢谢您的建议。我把它们投了赞成票,而不是接受它们,因为我觉得它们不能完全满足我的需要,但我将要做的事情肯定从中受益

我仍然认为,如果可能的话,应该避免使用SQL(出于我在评论中已经提到的所有原因),但我认为在这种情况下不能使用SQL,因此我决定为成员模型定义一个命名范围,如下所示:

#expired
Member.with_membership.find :all, :conditions => ['expires < ?', Time.now ]

#current
Member.with_membership.find :all, :conditions => ['started < ? AND expires > ?', Time.now, Time.now ]

#pending payment
Member.with_membership.find :all, :conditions => ['applied < ? AND paid IS NULL', Time.now ]
编辑:我最初将其定义为默认范围-但是我决定注意Kandaboggu关于复杂默认范围的警告,因此将其改为命名范围。当当前活动的成员身份存在时,为了处理排除续订的成员身份(开始日期在将来),该查询也比其他描述的查询要复杂一些。再次感谢Kandaboggu提供的查询要点和避免N+1选择的提示

scope :with_membership, lambda { 
select('members.*, m.applied AS applied, m.paid AS paid, m.start AS start, m.expiry as expiry').
joins("INNER JOIN (
  SELECT m3.*
  FROM memberships m3
  LEFT OUTER JOIN memberships m5 
  ON m3.member_id = m5.member_id 
  AND m5.created_at > m3.created_at
  AND m5.expiry > #{sanitize(Time.now)}
  WHERE m3.expiry > #{sanitize(Time.now)}
  AND m5.id IS NULL
  UNION
  SELECT m1.*
  FROM   memberships m1
  LEFT OUTER JOIN memberships m2 
  ON m1.member_id = m2.member_id 
  AND m2.created_at > m1.created_at 
  LEFT OUTER JOIN memberships m4 
  ON m1.member_id = m4.member_id 
  AND m4.expiry > #{sanitize(Time.now)}
  WHERE  m2.id IS NULL AND m4.id IS NULL
  ) m 
  on (m.member_id = members.id)") }         
我见过一些更漂亮的代码。但我的理由是——如果你必须有一个像这样可怕的SQL,你最好只在一个地方有它,而不是在每个不同的地方重复你可能需要的查询(如过期的成员,尚未付款的成员等)

有了上面的作用域,我就可以将额外的成员列作为
Member
上的普通列来处理,并很好地编写simp