6

I have the tables:

  • Candidates
  • CandidateLanguages
  • CandidateSkills

Each candidate may have more than 1 language and more than 1 skill

So for Candidate "FRED", his records in CandidateLanguages may be

FRED - ENGLISH
FRED - FRENCH

and his records in CandidateSkills may be

FRED - RUNNING
FRED - JUMPING

and for Candidate "JIM" his records in CandidateLanguages may be

JIM - ENGLISH

and his records in CandidateSkills may be

JIM - RUNNING

My query needs to select candidates that match multiple skills and languages.

So for example in English:

Select all of the candidates who speak ALL of the selected languages and have ALL of the selected skills...

Or put another way....

SELECT ALL candidates WHERE 
(language = 'FRENCH' AND language is 'ENGLISH') AND 
(skill = 'RUNNING' AND skill = 'JUMPING') 

Of the two candidates above, this should only return "FRED"

I understand that the problem is with trying to select multiple records from the Language and Skills table and I think that joins may be required, but now I am lost...

Cœur
  • 37,241
  • 25
  • 195
  • 267
  • Have you tried reversing the query? "give me all candidates that are missing one of the skills or missing one of the languages"? Sometimes rewording the problem can yield other/better solutions. If you stick a "not" in there in the right places it should give you the data you want, but the "not missing" part might be easier to formulate as a SQL query than the "having all" part. – Lasse V. Karlsen Jul 06 '13 at 19:41
  • thats 60 points.. 14 more and he reaches the daily limit, then he will have to wait for tomorrow.. – Raghavan Jul 06 '13 at 20:44
  • @user1974729 : 6 upvotes in a question means 30 rep, not 60. – ypercubeᵀᴹ Jul 06 '13 at 20:48
  • yups i know.. i just said 60 cos the multiplier looked so awesome.. i know it is 30.. – Raghavan Jul 06 '13 at 20:50

6 Answers6

4

The problem you are solving is called Relational Division.

See this article: Divided We Stand: The SQL of Relational Division and this question for a few ways to solve it: How to filter SQL results in a has-many-through relation

One way to solve it (which will be - in general - the most efficient):

SELECT ALL c.candidate
FROM Candidates c
  JOIN CandidateLanguages lang1
    ON  lang1.candidate = c.candidate 
    AND lang1.language = 'English'
  JOIN CandidateLanguages lang2
    ON  lang2.candidate = c.candidate 
    AND lang2.language = 'French'
  JOIN CandidateSkills sk1
    ON  sk1.candidate = candidate 
    AND sk1.skill = 'Running'
  JOIN CandidateSkills sk2
    ON  sk2.candidate = candidate 
    AND sk2.skill = 'Jumping' ;

Another way, which seems easier to write, especially if there are a lot of languages and skills involved, is to use two derived tables with GROUP BY in each of them:

SELECT ALL c.candidate
FROM Candidates c
  JOIN 
    ( SELECT candidate
      FROM CandidateLanguages
      WHERE language IN ('English', 'French')
      GROUP BY candidate
      HAVING COUNT(*) = 2                     -- the number of languages
    ) AS lang
      ON  lang.candidate = c.candidate 
  JOIN 
    ( SELECT candidate
      FROM CandidateSkills 
      WHERE skill IN ('Running', 'Jumping')
      GROUP BY candidate
      HAVING COUNT(*) = 2                     -- the number of skills
    ) AS sk
      ON  sk.candidate = c.candidate ;
Community
  • 1
  • 1
ypercubeᵀᴹ
  • 113,259
  • 19
  • 174
  • 235
1

If you want all skills and all languages, simply counting the multiplications will be enough.

select c.id
from candidate c 
join candidateLanguage cl on c.id = cl.candidateId
join language l on cl.languageId = l.id
join candidateSkill cs on c.id = cd.candidateId
join skill s on s.id = cs.skillId
group by c.id
having count(*) = 4

The having condition can be expressed as

having count(*) = 
    (select count(*) from skill) * (select count(*) from language)

What do I do here?

  • Listing all possible Candidate-Language-Skill triplets
  • Grouping them by candidate
  • if the count equals to (count of skills) * (count of languages) then for this candidate all combinations are present

EDIT:

If you only want a subset of languages and skills, you can filter it:

select c.id
from candidate c 
join candidateLanguage cl on c.id = cl.candidateId
join language l on cl.languageId = l.id
join candidateSkill cs on c.id = cd.candidateId
join skill s on s.id = cs.skillId
where l.name in ('English', 'French')
  and s.name in ('RUNNING', 'JUMPING')
group by c.id
having count(*) = 4

The difference here is that you can count only those skills and languages that are matching your criteria.

gaborsch
  • 15,408
  • 6
  • 37
  • 48
0

Not elegant, but efficient.

SELECT 
  *
FROM
  Candidates c
WHERE 
  (SELECT COUNT(*) 
   FROM   CandidateLanguages cl 
   WHERE  cl.candidateId = c.candidateId AND cl.language in ('FRENCH', 'ENGLISH')
  ) = 2
  AND
  (SELECT COUNT(*) 
   FROM   CandidateSkills cs 
   WHERE  cs.candidateId = c.candidateId AND cs.skill in ('RUNNING', 'JUMPING')
  ) = 2
Tomalak
  • 332,285
  • 67
  • 532
  • 628
0

If your data query can be written as "Give me all candidates that have all the known skills that we have listed in a table, and all known languages we have listed in a different table", and not just "all the candidates with english and french, and both jumping and running", you can use one of these data-driven queries:

select
    *
from
    Candidates as C
where
    (select count(*) from CandidateLanguages where CandidateName = C.Name) = (select count(*) from Languages)
and (select count(*) from CandidateSkills where CandidateName = C.Name) = (select count(*) from Skills)
go

select
    *
from
    Candidates
where
    Name not in (
        select 
            C.Name
        from
            (Candidates as C cross join Languages as L)
            left join CandidateLanguages as CL on C.Name = CL.CandidateName and L.Name = CL.LanguageName
        where
            CL.CandidateName is null
    )
and Name not in (
        select
            C.Name
        from
            (Candidates as C cross join Skills as S)
            left join CandidateSkills as CS on C.Name = CS.CandidateName and S.Name = CS.LanguageName
        where
            CS.CandidateName is null
    )
go

Full example code that can be tested in LINQPad is available here. (You may have to create the empty database)

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
-1
 select candidate-name,resulttblskills1.sumCOLRATIOSKILLS,resulttbllanguages1.sumCOLRATIOLanguages from candidates candidates1

    join (select  count(*) as sumCOLRATIOskills
          from candidateskills skills1
          where skills1.requiredskills in ('jumping','canoeing','mostlygoofing'
          group by id
          ) as resulttblskills1 on resulttblskills1.id = candidates1.id 

    join (select  count(*) as sumCOLRATIOLANGUAGES
          from candidatelanguages languages1     
          where languages1.requiredlanguages in ('French','english','esparanto')
          group by id ) as resulttbllanguages1 on resulttbllanguages1.id = candidates1.id

    where resulttblskills1.sumCOLRATIOSKILLS > 1
          and resulttbllanguages1.sumCOLRATIOLANGUAGES > 1
Raghavan
  • 637
  • 3
  • 12
  • Do you have `id` column on `resulttblskills1` and `resulttblskills2`? – Camouflage Jul 06 '13 at 20:27
  • ya well u r grouping by ID to get the sum for each id and joining the summed table on the primary keys.. – Raghavan Jul 06 '13 at 20:32
  • 1
    I can't see it on the select line, probably you forgot to add it there. Also I think you want to correct the `sumCOLRATIOSKILLS > 1` condition as well. – Camouflage Jul 06 '13 at 20:36
  • well i just made up some columns there since i dont see the table design and mostly did not want to go with FK1=PK1 and FK2=PK2... i think it is select from Maintable join (select sum from reqtable1) as sumreqtable1 on Foreign Key relationship join (select sum from reqtable2) as sumreqtable2 on Foreign Key Relationship where ur required sum conditions ... – Raghavan Jul 06 '13 at 20:41
  • 1
    Please stop using "txt spk". [(A relevant question on meta.)](http://meta.stackexchange.com/questions/105497) – Christoffer Lette Jul 06 '13 at 21:45
-2

You are describing a many to many relationship between candidates and skills and also between candidates and languages. Hopefully your database has the requisite tables. The query would resemble this:

select yourfields
from candidate c join candidateLanguage cl on c.id = cl.candidateId
join languages l on cl.languageId = l.id
join candidateSkill cs on c.id = cd.candidateId
join skill s on s.id = cs.skillId
where l.language in ('FRENCH', 'ENGLISH')
and s.skill in ('RUNNING', 'JUMPING')
Dan Bracuk
  • 20,699
  • 4
  • 26
  • 43
  • 4
    That query is not equivalent to the description, it'll return anyone speaking either language with either skill – AD7six Jul 06 '13 at 20:00