ActiveRecord / SQL选择所有ModalA,其中没有关联的ModelB(如果存在)的实例属于ModelC

时间:2016-03-11 21:20:44

标签: sql ruby-on-rails activerecord

我正在尝试使用ActiveRecord编写一个相当复杂的查询(虽然我更愿意使用原始SQL,但是需要一些帮助来改进它。)

我有以下型号:

class Company < ActiveRecord::Base
    has_many :contacts
    has_many :workplace_stories
end

class WorkplaceStory < ActiveRecord::Base
    belongs_to :company
    has_many :questions
    has_many :invites
end

class Invite < ActiveRecord::Base
    belongs_to :workplace_story
    belongs_to :contact
    has_one :response
end

class Response < ActiveRecord::Base
    belongs_to :invite
    has_many :answers
end

class Contact < ActiveRecord::Base
    belongs_to :company
    has_many :invites
end

我想编写一个查询,该查询将选择所有没有任何属于给定联系人的邀请的工作场所故事,其中邀请有响应。换句话说,给出以下内容:

company = Company.last
=> #<Company:0x007fef5f58bd98>
contact = company.contact.last
=> #<Contact:0x007fef5f58bd98>
contact.invites.count
=> 1
contact.invites.first.response
=> #<Response:0x007fef5f58bd98>
company.workplace_stories.count
=> 5
company.workplace_stories.map { |x| x.invites.count }
=> [0, 0, 1, 2, 2]

我不想写一个查询,就像我下面的内容一样,它将返回所有工作场所的故事,其中没有任何带有响应的邀请(如果有的话)属于contact。下面的查询接近我的目标,但它只选择至少有一个邀请的故事 - 在这种情况下,有两个。

stories = workplace_stories.joins(:invites).where.not(invites: {contact: contact}).order(updated_at: :desc).uniq.count
=> 2

1 个答案:

答案 0 :(得分:0)

对于这样的查询,我希望首先在SQL中使用它,然后决定如何在ActiveRecord中表达它。看起来这是您正在寻找的查询:

SELECT  *
FROM    workplace_stories wps
WHERE   NOT EXISTS (SELECT  1
                    FROM    invites i
                    INNER JOIN  responses r
                    ON      r.invite_id = i.id
                    WHERE   i.contact_id = ?
                    AND     i.workplace_story_id = wps.id)

就个人而言,我喜欢写一个范围,这样可以很容易地重复使用,并且很容易与其他条件结合使用:

class WorkplaceStory

  scope :without_response_by, -> (contact_id) {
    where(<<-EOQ, contact_id)
      NOT EXISTS (SELECT  1
                  FROM    invites i
                  INNER JOIN  responses r
                  ON      r.invite_id = i.id
                  WHERE   i.contact_id = ?
                  AND     i.workplace_story_id = workplace_stories.id)
    EOQ
  }

  # ...