简体   繁体   中英

SQL Query to find records having certain records in a has_many through relationship

I have following models:

# contributor.rb

class Contributor < ApplicationRecord
  has_many :skill_groupings
  has_many :skills, through: :skill_groupings
end

# skill_grouping.rb

class SkillGrouping < ApplicationRecord
  belongs_to :skill
  belongs_to :contributor
end

# skill.rb

class Skill < ApplicationRecord
  has_many :skill_groupings
  has_many :contributors, through: :skill_groupings
end

I want to search (SQL query) for the contributors whose skill_ids contain all the values included in an array.

Suppose data are the following:

contributor_1.skill_ids: [2, 3, 6, 7]
contributor_2.skill_ids: [4, 7]
contributor_3.skill_ids: [9]
contributor_4.skill_ids: []

wanted_ids = [3, 7]

Contributor.joins(:skill_groupings).where(skill_groupings: { skill_id: wanted_ids }).distinct returns the contributors which have 3 or 7 in their skill ids, hence [contributor_1, contributor_2] . This is not what I want.

The result of the query should be [contributor_1] , because only contributor_1 has all the skill ids in wanted_ids .

Unfortunately, skill_ids seems to be only an attribute introduced by Rails and not available in SQL.

How can I achieve this only by means of SQL queries?

Assuming that a contributor cannot have the same skill more than once, the simplest way I can see going about this is to find all the contributors with these skills that have the same number of skills as the skills passed in eg

Contributor.joins(:skills)
  .where(skills: { id: wanted_ids })
  .group(:id)
  .having("COUNT(skills.id) = #{wanted_ids.uniq.size}"))

This will result in the following SQL given wanted_ids = [3, 7]

SELECT 
  contributors.*
FROM 
  contributors 
  INNER JOIN skill_groupings ON skill_groupings.contributor_id = contributors.id
  INNER JOIN skills ON skills.id = skill_groupings.skill_id
WHERE
  skills.id IN (3,7)
GROUP BY 
  contributors.id 
HAVING 
  COUNT(skills.id) = 2

This will only work for PostgreSQL because this SQL server allows for grouping by a primary key while still selecting the whole row of columns. For other databases you will need a sub subquery such as:

Contributor.where(id: 
  Contributor.joins(:skills)
    .select(:id)
    .where(skills: { id: wanted_ids })
    .group(:id)
    .having("COUNT(skills.id) = #{wanted_ids.uniq.size}")
)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM