【问题标题】:Filtering an association by missing records in Rails通过 Rails 中的缺失记录过滤关联
【发布时间】:2021-07-01 15:05:32
【问题描述】:

在我正在开发的 Rails 应用程序中,我有几个不同的模型因此相关联(为了清楚起见,进行了精简):

group.rb

class Group < ApplicationRecord
  has_many :members, class_name: 'GroupMember'
  has_many :newsletters
end

group_member.rb

class GroupMember < ApplicationRecord
  belongs_to :group,

  has_many :subscriptions, inverse_of: :group_member, class_name: 'Newsletter::Subscriber'

  scope :subscribed_to, ->(newsletter_id) { joins(:subscriptions).merge(Newsletter::Subscriber.where(["newsletter_id = ?", newsletter_id])) }
  scope :not_subscribed_to, ->(newsletter_id) { where.missing(:subscriptions) }
end

newsletter.rb

class Newsletter < ApplicationRecord
  acts_as_tenant :group

  has_many :subscribers, inverse_of: :newsletter
end

newsletter/subscriber.rb

class Newsletter::Subscriber < ApplicationRecord
  acts_as_tenant :group

  belongs_to :newsletter, inverse_of: :subscribers
  belongs_to :group_member, class_name: 'GroupMember', inverse_of: :subscriptions
end

鉴于上述相关模型,这是我正在使用的框架:

  • 每个组都有 n 个组成员和 n 个时事通讯。
  • 每个组成员可以有多个时事通讯订阅(组中的每个时事通讯一个)

我正在尝试做的事情(到目前为止没有成功)是找出组中的哪些成员没有订阅与该组关联的特定时事通讯。

我可以使用GroupMember 对象上的以下范围找出确实有订阅的成员:

  scope :subscribed_to, ->(newsletter_id) { joins(:subscriptions).merge(Newsletter::Subscriber.where(["newsletter_id = ?", newsletter_id])) }

这让我可以查询,例如,current_group.members.subscribed_to(current_group.newsletters.first.id)

但是,我不确定如何否定这一点并获得与该组成员相反的结果。也就是说,成员未订阅该特定时事通讯。我目前定义的:not_subscribed_to 范围并没有删减它,因为它没有考虑到我指的是哪个时事通讯。

【问题讨论】:

  • 为什么是 标签?我没有看到上面的 SQL。您期望得到 SQL 答案吗?
  • @jarlh 确实,我在这里使用的是活动记录而不是原始 SQL。我怀疑答案将涉及编写一些我还不完全理解的原始 SQL 连接。

标签: sql ruby-on-rails activerecord


【解决方案1】:

给定变量newsletter_id

另一种方法是将WHERE NOT EXISTS(...) 与子查询一起使用:

Member
  .where(
    'NOT EXISTS(
       SELECT 1 FROM "subscriptions" 
       WHERE "subscriptions"."member_id" = "members"."id" 
       AND   "subscriptions"."newsletter_id" = ?
    )', newsletter_id
  )

翻译成阿雷尔:

Member.where(
  Subscription.select('1')
              .where(
                Subscription.arel_table[:member_id].eq(Member.arel_table[:id])
              ).where(
                newsletter_id: newsletter_id
              ).arel.exists.not
)

或分组、计数和拥有:

Member.group(:id)
      .left_joins(:subscriptions)
      .where(subscriptions: { newsletter_id: newsletter_id })
      .having(Subscription.arel_table[:id].count.eq(0))

【讨论】:

  • 很棒的答案,麦克斯! LEFT OUTER 连接对我不起作用,因为(我认为)它假定subscriptions 表将包含nilnewsletter_ids 的行。但是,WHERE NOT EXISTS 查询对于我在这里尝试做的事情很有意义。我在我的范围内使用了它的 Arel 版本,它看起来正在做我需要它做的事情。谢谢!
  • 嗯我有点困惑为什么第一个不起作用。基本上,当您加入并设置“WHERE subscription.member_id = NULL”时,您应该得到没有匹配的行,因为否则您将在连接表中获得至少一个匹配。
  • 好吧,如果我像这样直接查询WHERE 条件:Newsletter::Subscriber.where({ newsletter_id: newsletter_id, group_member_id: nil }) 我永远不会返回任何行,因为group_member_id 永远不会是nil。在这种情况下 - 如果我错了,请纠正我 - 没有什么可以加入小组成员
猜你喜欢
  • 2011-07-13
  • 1970-01-01
  • 2016-01-11
  • 1970-01-01
  • 2015-01-27
  • 1970-01-01
  • 1970-01-01
  • 2011-04-22
  • 1970-01-01
相关资源
最近更新 更多