开发者

Can this sql query be simplified?

I have the following tables:

Person, {"Id", "Name", "LastName"}

Sports, {"Id" "Name", "Type"}

SportsPerPerson, {"Id", "PersonId", "SportsId"}

For my query I want to get all the Persons that excersise a specific Sport whereas I only have the Sports "Name" attribute at my disposal. To retrieve the correct rows I've figured out the following queries:

SELEC开发者_运维百科T *
FROM Person
WHERE Person.Id in 
(
    SELECT SportsPerPerson.PersonId FROM SportsPerPerson
    INNER JOIN Sports on SportsPerPerson.SportsId = Sports.Id
    WHERE Sports.Name = 'Tennis'
)
AND Person.Id in 
(
    SELECT SportsPerPerson.PersonId FROM SportsPerPerson
    INNER JOIN Sports on SportsPerPerson.SportsId = Sports.Id
    WHERE Sports.Name = 'Soccer'
)

OR

SELECT *
FROM Person
WHERE Id IN
    (SELECT PersonId FROM SportsPerPerson WHERE SportsId IN 
        (SELECT Id FROM Sports WHERE Name = 'Tennis'))
AND Id IN
    (SELECT PersonId FROM SportsPerPerson WHERE SportsId IN 
        (SELECT Id FROM Sports WHERE Name = 'Soccer'))

Now my question is, isn't there an easier way to write this query? Using just OR won't work because I need the person who play 'Tennis' AND 'Soccer'. But using AND also doesn't work because the values aren't on the same row.


You can use another JOIN to avoid the second IN. The sub-select only returns those persons that play both Tennis and Soccer:

SELECT *
FROM Person
WHERE Person.Id IN
(
    SELECT spp1.PersonId
    FROM SportsPerPerson spp1
    JOIN SportsPerPerson spp2 ON ( spp2.PersonId = spp1.PersonId )
    JOIN Sports s1 on spp1.SportsId = s1.Id
    JOIN Sports s2 on spp2.SportsId = s2.Id
    WHERE s1.Name = 'Tennis'
      AND s2.Name = 'Soccer'
)


You should use two joins in the query:

SELECT *
FROM Person p INNER JOIN SportsPerPerson spp1 ON (p.PersonId = spp1.PersonId)
              INNER JOIN Sports s1 ON (s1.SportsIN = spp1.SportId)
              INNER JOIN SportsPerPerson spp2 ON (p.PersonId = spp2.PersonId)
              INNER JOIN Sports s2 ON (s2.SportId = spp2.SportId)
    WHERE s1.Name = 'Tennis' AND s2.Name='Soccer'


The trick is to use aliases so that you can use the same tables multiple times:

SELECT p.*
FROM Person p
INNER JOIN SportsPerPerson spa
  ON p.Id = spa.PersonId
INNER JOIN Sports sa
  ON spa.SportsId = sa.Id
INNER JOIN SportsPerPerson spb
  ON p.Id = spb.PersonId
INNER JOIN Sports sb
  ON spb.SportsId = sb.Id
WHERE
  sa.Name = 'Tennis'
  AND sb.Name = 'Soccer'


This:

SELECT  *
FROM    Person p
WHERE   (
        SELECT  COUNT(*)
        FROM    Sports s
        JOIN    SportsPerPerson sp
        ON      sp.SportsID = s.id
        WHERE   s.name IN ('Tennis', 'Soccer')
                AND sp.PersonID = p.id
        ) = 2

or this:

SELECT  p.*
FROM    (
        SELECT  sp.PersonID
        FROM    Sports s
        JOIN    SportsPerPerson sp
        ON      sp.SportsID = s.id
        WHERE   s.name IN ('Tennis', 'Soccer')
        GROUP BY
                sp.PersonID
        HAVING  COUNT(*) = 2
        ) q
JOIN    person p
ON      p.id = q.personID

You need to declare a UNIQUE KEY or a PRIMARY KEY on SportsPerPerson (sportsid, personid) for this to work correctly and fast.


The query you need is:

SELECT p.ID, p.Name, p.LastName
FROM Person p
JOIN SportsPerPerson sp ON p.ID = sp.PersonID 
JOIN Sports s ON sp.SportsID = s.ID
WHERE s.Name = 'Football'

That said, as an aside, the ID key on the SportsPerPerson table is entirely unnecessary to implement the many to many relationship you have. Using the PersonID and SportID columns as a composite primary key would be enough.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜