ruby-on-rails 对具有不同关联集的记录的Rails查询

x6h2sr28  于 2023-01-22  发布在  Ruby
关注(0)|答案(1)|浏览(161)

我正在尝试查询具有一组特定关联的记录。这是一种通过联接进行的存在查询。我想查询恰好具有skill_1、skill_2、skill_3和level_a、level_B、level_c的用户

class User < ApplicationRecord
  has_many :skills
end

class Skill < ApplicationRecord
  belongs_to :user
end

# example of current attempt and intended use
def user_exists?(skills, levels)
  User.joins(:skills).where(skill_name: skills, skill_level: levels)
end

这将给予用户提供技能中的技能和级别中的级别,这将返回技能和级别集的各种组合,但我只希望用户在这些级别上拥有这些技能。
我如何编写该查询?

6jjcrrmo

6jjcrrmo1#

假设我有skills = ['a','b','c']levels = [1,2,3]
根据您的问题,有2种可能的迭代:
1.这是2的乘积,例如[["a", 1], ["a", 2], ["a", 3], ["b", 1], ["b", 2], ["b", 3], ["c", 1], ["c", 2], ["c", 3]];或
1.它应该是[["a", 1], ["b", 2], ["c", 3]]

    • 备选案文1**:我们可以将其解为:
def user_exists?(skills, levels)
  User
   .joins(:skills)
   .where(skill_name: skills, skill_level: levels)
   .group(:id)
   .having(Arel.star.count.eq([skills.size,levels.size].max))
end

这将生成以下SQL:

SELECT 
  users.* 
FROM 
  users 
  INNER JOIN skills ON skills.user_id = users.id
WHERE 
  skills.name IN ('a','b','c') AND skills.levels IN (1,2,3) 
GROUP BY 
  users.id 
HAVING 
  COUNT(*) = 3
    • 备选案文2**:我们可以将其解为:(假设skills.size == levels.size
def user_exists?(skills, levels)
  groups = skills.zip(levels).map do |g| 
    Arel::Nodes::Grouping.new(g.map(&Arel::Nodes.method(:build_quoted)))
  end
  User
    .joins(:skills)
    .where(Arel::Nodes::NamedFunction.new('ROW',
             [Skill.arel_table[:name],Skill.arel_table[:level]])
           .in(groups))
    .group(:id)
    .having(Arel.star.count.eq(skills.size))
end

这将生成以下SQL:

SELECT 
  users.* 
FROM 
  users 
  INNER JOIN skills ON skills.user_id = users.id
WHERE 
  ROW(skills.name,skills.level) IN (('a',1),('b',2),('c',3))
GROUP BY 
  users.id
HAVING 
  COUNT(*) = 3

备选案文2也可写成:

conditions = skills.zip(levels).map do |skill_name,level| 
   Arel::Nodes::Grouping.new(
     Skill.arel_table[:name].eq(skill_name).and(
       Skill.arel_table[:level].eq(level))
   )
  end.reduce(&:or)

  # sub: Arel::Nodes::NamedFunction.new('ROW', [Skill.arel_table[:name],Skill.arel_table[:level]]).in(groups)
  # with: groups

这将为以下技能生成WHERE子句

(skills.name = 'a' and skills.level = 1) OR 
(skills.name = 'b' and skills.level = 2) OR
(skills.name = 'c' and skills.level = 3)

还有其他方法可以完成这些查询,例如使用交集查询,但构造有点不那么直接。

相关问题