【问题标题】:How to write set-operation queries in ActiveRecord without writing INTERSECT, UNION, or EXCEPT in SQL?如何在 ActiveRecord 中编写集合操作查询而不在 SQL 中编写 INTERSECT、UNION 或 EXCEPT?
【发布时间】:2016-06-28 03:45:51
【问题描述】:

我有一个用于管理志愿者的 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
  3. 我想在任何@lists@skills 中找到所有志愿者。
  4. 我想在任何@lists 和所有@skills 上找到所有志愿者。

在看到这个StackOverflow question 之后,我使用.find_by_sql 创建了这个问题的解决方案。当查询涉及查找所有@skills 的志愿者时,我构造了一个INTERSECT 查询。当查询涉及使用 ANY @skills 查找志愿者时,我构造了一个 UNION 查询。当查询涉及查找没有@skills 的志愿者时,我使用EXCEPT 格式化查询。

不幸的是,这种解决方案不如使用 ActiveRecord 的解决方案友好。我对编程很陌生,但我试图解决的问题似乎相当简单/常见。有没有更好的利用 ActiveRecord 的解决方案?

我非常感谢任何想法!

【问题讨论】:

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


    【解决方案1】:

    您可以使用 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 部分运行良好。

    【讨论】:

    • 非常感谢戴夫!我认为您的解决方案的一个好处是它们允许进一步链接 ActiveRecord 查询 - 这使得它们整体上更容易使用。
    • 是的,这就是 ActiveRecord 的总体优势。否则我会放弃自己更多地使用 SQL!
    猜你喜欢
    • 1970-01-01
    • 2017-09-21
    • 1970-01-01
    • 2017-07-26
    • 1970-01-01
    • 1970-01-01
    • 2017-08-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多