如何在ActiveRecord中编写集合操作查询而不在SQL中编写INTERSECT,UNION或EXCEPT?

时间:2016-03-13 00:08:49

标签: ruby-on-rails ruby rails-activerecord

我有一个用于管理志愿者的Rails应用程序,我正在寻找编写复杂SQL查询以过滤所有志愿者记录的“Rails方式”。我有以下型号:

class Volunteer < ActiveRecord::Base
  has_many :volunteer_skills
  has_many :skills, through: :volunteer_skills
  has_many :volunteer_lists
  has_many :lists, through: :volunteer_lists
end

class VolunteerSkill < ActiveRecord::Base
  belongs_to :volunteer
  belongs_to :skill
end

class VolunteerList < ActiveRecord::Base
  belongs_to :volunteer
  belongs_to :list
end

如果@listsList个ID的数组,而@skillsSkill个ID的数组

  1. 我希望找到所有志愿者@lists,而不是@skills
  2. 我希望找到所有志愿者@lists,而不是@skills。{/ li>
  3. 我想在@lists任意@skills找到所有志愿者。
  4. 我希望在所有@lists的所有@skills上找到所有志愿者。
  5. 看到此StackOverflow question后,我使用.find_by_sql创建了此问题的解决方案。当查询涉及查找包含ALL @skills的志愿者时,我构建了INTERSECT查询。当查询涉及找到任何@skills的志愿者时,我构建了一个UNION查询。如果查询涉及查找没有@skills的志愿者,我会使用EXCEPT格式化查询。

    不幸的是,这个解决方案并不像使用ActiveRecord那样友好。我对编程很陌生,但我试图解决的问题似乎相当简单/普遍。是否有更好的解决方案可以使用ActiveRecord?

    我非常感谢任何想法!

1 个答案:

答案 0 :(得分:2)

您可以使用ActiveRecord完成大部分工作,但仍需要使用一些SQL片段。

首先,为所有@lists上的所有志愿者制作一个范围:

class Volunteer < ActiveRecord::Base
  scope :on_any_list -> lists { joins(:volunteer_list).where(volunteer_id: lists) }
end

然后为每个技能标准添加条件。

任何@skills的志愿者都很容易:

on_any_list(@lists).joins(:volunteer_skills).where(skill_id: @skills).distinct

对于所有@skills的志愿者,您需要构建一个查询:

query = on_any_list(@lists)
@skills.each do |skill|
  query = query.where("exists (select 1 from volunteer_skills vs " +
                      "where vs.volunteer_id = volunteers.id and skill_id = ?)", skill)
end

不幸的是,ActiveRecord的not不允许您构建上述任何一个的相反查询,因此您需要从头开始执行缺少技能的查询。

没有全部@skills的志愿者:

on_any_list(@lists).
  where("not exists (select 1 from volunteer_skills vs " +
        "where vs.volunteer_id = volunteers.id and skill_id in (?))", @skills)

最后,最狡猾的志愿者没有任何@skills

on_any_list(@lists).
  joins("left join volunteer_skills vs on volunteers.id = vs.volunteer_id").
  where(skill_id: @skills)
  where("volunteer_skills.id is null").
  distinct

(有an AREL way to do the outer join,但我认为这不容易。)

编写和理解这些技术所需的各种技术让我想知道,作为一个整体,它们是否真的比SQL版本更好。至少on_any_lists部分效果很好。