简体   繁体   中英

Select all entries from one table which has two specific entries in another table

So, I have 2 tables defined like this:

CREATE TABLE tblPersons (
    id   INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT
);

CREATE TABLE tblHobbies (
    person_id INTEGER REFERENCES tblPersons (id),
    hobby     TEXT
);

And for example I have 3 person added to tblPersons :

1 | John
2 | Bob
3 | Eve

And next hobbies in tblHobbies :

1 | skiing
1 | serfing
1 | hiking
1 | gunsmithing
1 | driving
2 | table tennis
2 | driving
2 | hiking
3 | reading
3 | scuba diving

And what I need, is query which will return me a list of person who have several specific hobbies.

The only thing I could've come up with, is this:

SELECT id, name FROM tblPersons
    INNER JOIN tblHobbies as hobby1 ON hobby1.hobby = 'driving'
    INNER JOIN tblHobbies as hobby2 ON hobby2.hobby = 'hiking'
    WHERE tblPersons.id = hobby1.person_id and tblPersons.id = hobby2.person_id;

But it is rather slow. Isn't there any better solution?

First, you don't have a Primary Key on tblHobbies this is one cause of slow query (and other problems). Also you should consider creating a index on tblHobbies.hobby .

Second, I'd to advice you to create a third table to evidence N:N cardinality that exists in your model and avoid redundant hobbies. Something like:

--Person
CREATE TABLE tblPersons (
    id   INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT
);

--Hobby
CREATE TABLE tblHobbies (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    hobby TEXT
);

--Associative table between Person and Hobby
CREATE TABLE tblPersonsHobbies (
    person_id INTEGER REFERENCES tblPersons (id),
    hobby_id INTEGER REFERENCES tblHobbies (id),
    PRIMARY KEY (person_id, hobby_id)
);

Adds an extra table but it's worth it.

--Query on your current model
SELECT id, name FROM tblPersons
    INNER JOIN tblHobbies as hobby1 ON  tblPersons.id = hobby1.person_id
    WHERE hobby1.hobby IN ('driving', 'hiking');

--Query on suggested model
SELECT id, name FROM tblPersons
    INNER JOIN tblPersonsHobbies as personsHobby ON  tblPersons.id = personsHobby.person_id
    INNER JOIN tblHobbies as hobby1 ON hobby1.id = personsHobby.hobby_id
        WHERE hobby1.hobby IN ('driving', 'hiking');

You can aggregate the hobbies table to get persons with both hobbies:

select person_id
from tblhobbies
group by person_id
having count(case when hobby = 'driving' then 1 end) > 0
   and count(case when hobby = 'hiking' then 1 end) > 0

Or better with a WHERE clause restricting the records to read:

select person_id
from tblhobbies
where hobby in ('driving', 'hiking')
group by person_id
having count(distinct hobby) =2

(There should be a unique constraint on person + hobby in the table, though. Then you could remove the DISTINCT . And as I said in the comments section it should even be person_id + hobby_id with a separate hobbies table. EDIT: Oops, I should have read the other answer. Michal suggested this data model three hours ago already :-)

If you want the names, select from the persons table where you find the IDs in above query:

select id, name
from tblpersons
where id in
(
  select person_id
  from tblhobbies
  where hobby in ('driving', 'hiking')
  group by person_id
  having count(distinct hobby) =2
);

With the better data model you'd replace

  from tblhobbies
  where hobby in ('driving', 'hiking')
  group by person_id
  having count(distinct hobby) =2

with

  from tblpersonhobbies
  where hobby_id in (select id from tblhobbies where hobby in ('driving', 'hiking'))
  group by person_id
  having count(*) =2

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