I have below tables
User
user_id name 101 Tony 102 Skyle 103 Kenne
Intrest
intrest_id intrest_name 201 Eating 202 Sleeping 203 Drinking
Hobby
hobby_id hobby_name 301 Smoking 302 Hiking 303 Browsing
User_Intrest
user_id intrest_id 101 201 102 201 102 202 103 201 103 202 103 203
User_Hobby
user_id hobby_id 101 301 102 301 102 302 103 301 103 302 103 303
Now to find user ids who have both interests Eating
and Sleeping
I have written
select u.user_id from user u, intrest i, user_intrest ui where u.user_id = ui.user_id and i.intrest_id = ui.intrest_id and i.intrest_name in ('Eating', 'Sleeping') group by u.user_id having count(i.intrest_name) = 2
Output
user_id 102 103
Same as above I also can find user ids with hobbies Smoking
, Hiking
, Browsing
as below
select u.user_id from user u, hobby h, user_hobby uh where u.user_id = uh.user_id and h.hobby_id = uh.hobby_id and h.hobby_name in ('Smoking', 'Hiking', 'Browsing') group by u.user_id having count(i.intrest_name) = 3
Output
user_id 103
Now I want to mix these two optionally in a way that if only interests are passed then users with those interests will be found or if only hobbies are passed then users with those hobbies are found or if both interests and hobbies are passed then users with those interests as well as hobbies are found
Update: This is part of a spring data rest api with native query where query parameters are optional. Below is a working example for finding users for intrests. Now I want to expand it to include hobbies so both will be used if both are passed in the request to find users
@RestResource(path = "getUserByIntrestsAndHobbies", rel = "getUserByIntrestsAndHobbies") @Query(value = "SELECT u FROM User u WHERE (COALESCE(:intrests, NULL) IS NOT NULL AND u.userId IN (SELECT u.userId " + "FROM User u, Intrest i, UserIntrest ui " + "WHERE u.userId = ui.userId AND i.intrestId = ui.intrestId " + "AND i.intrestName IN (:intrests) " + "GROUP BY u.userId " + "HAVING (:intrestsSize IS NULL OR :intrestsSize = count(i.intrestName))))" ) Page<User> getUsersByIntrestsAndHobbies(@Param("intrests") List<String> intrests, @Param("intrestsSize") Long intrestsSize, @Param("hobbies") List<String> hobbies, @Param("hobbiesSize") Long hobbiesSize, Pageable pageable);
Advertisement
Answer
OK, the problem becomes a lot clearer when you use explicit joins (instead of the implicit, depreciated, comma-separated joins). You then see that you need to use left
joins to allow a record to be returned when there is match.
select u.[user_id] from [user] u left join user_intrest ui on ui.[user_id] = u.[user_id] left join intrest i on i.intrest_id = ui.intrest_id and i.intrest_name in ('Eating', 'Sleeping') left join user_hobby uh on uh.[user_id] = u.[user_id] left join hobby h on h.hobby_id = uh.hobby_id and h.hobby_name in ('Smoking', 'Hiking', 'Browsing') group by u.[user_id] having count(distinct i.intrest_name) = 2 and count(distinct h.hobby_name) = 3;
Its best practice not to use reserved words such as
user
oruser_id
as you will always need to escape them.Aside from the
id
column, there is no need to prepend the table name to a column name e.g.hobby_name
should just bename
– you’re just giving yourself more typing as it stands.Interest is not spelt Intrest 🙂
If you provide DDL/DML for the sample data then its a lot easier for us to test.