I guess this is a common setting, but as I don’t do that much SQL work, I can’t get my head around this one… So, I’ve got a bunch of songs that have certain features (style of music, mood etc.) and I would like to select songs that are attributed some of these features (e. g. songs that are happy and euphoric).
SONG +----+----------+ | id | title | +----+----------+ | 1 | song #1 | +----+----------+ | 2 | song #2 | +----+----------+ FEATURE +----+-------+----------+ | id | name | value | +----+-------+----------+ | 1 | mood | sad | +----+-------+----------+ | 2 | mood | happy | +----+-------+----------+ | 3 | mood | euphoric | +----+-------+----------+ | 4 | style | rock | +----+-------+----------+ | 5 | style | jazz | +----+-------+----------+ SONG_FEATURE +---------+------------+ | song_id | feature_id | +---------+------------+ | 1 | 1 | +---------+------------+ | 2 | 1 | +---------+------------+ | 2 | 2 | +---------+------------+
I would like to select all the songs that have certain features with an AND
condition. I would use this query for the OR
-case.
SELECT s.*, f.* FROM song_feature sf LEFT JOIN song s ON s.id = sf.song_id LEFT JOIN feature f ON f.id = sf.feature_id WHERE ( f.name = 'style' AND f.value = 'pop' ) OR /* <-- this works, but I would like an AND condition */ ( f.name = 'style' AND f.value = 'pop' ) GROUP BY sf.song_id;
But this obviously does not work for the AND
condition, so I guess I’m on the wrong track here… Any hints will be greatly appreciated.
Advertisement
Answer
You can do it with aggregation, if you filter the resultset of the joins and set the condition in the HAVING clause:
SELECT s.id, s.title FROM SONG s INNER JOIN SONG_FEATURE sf ON sf.song_id = s.id INNER JOIN FEATURE f ON f.id = sf.feature_id WHERE (f.name, f.value) IN (('mood', 'sad'), ('mood', 'happy')) GROUP BY s.id, s.title HAVING COUNT(DISTINCT f.name, f.value) = 2
See the demo.
Results:
> id | title > -: | :------ > 2 | song #2