Search code examples
mysqlmany-to-many

MYSQL: select from many-to-many linking table


I have 3 tables:

1) exercise: exercise_id, name

2) equipment: equipment_id, name

3) exercise_equipment: exercise_id, equipment_id - this is the linking table between exercise and equipment. In this table, one exercise can require multiple pieces of equipment, and the same equipment can be used for many exercises.

CREATE TABLE exercise
    (`ex_id` int, `name` varchar(30))
;   
INSERT INTO exercise
    (`ex_id`, `name`)
VALUES
    (1, 'push ups'),
    (2, 'sit ups'),
    (3, 'squats'),
    (4, 'push ups with a fitness ball')
;

CREATE TABLE equipment
    (`eq_id` int, `name` varchar(30))
;
INSERT INTO equipment
    (`eq_id`, `name`)
VALUES
    (1, 'none'),
    (2, 'mat'),
    (3, 'ball')
;

CREATE TABLE exercise_equipment
    (`ex_id` int, `eq_id` int)
;
INSERT INTO exercise_equipment
    (`ex_id`, `eq_id`)
VALUES
    (1, 2),
    (2, 2),
    (3, 1),
    (4, 2),
    (4, 3)
;

What I need to do is select all the exercises that the user can do with the equipment that he has (for example, 1 and 2 - no equipment or mat).

I have found some examples and tried the following queries with inner join and where in:

SELECT ex.ex_id, ex.name from exercise ex 
LEFT JOIN exercise_equipment exeq ON ex.ex_id = exeq.ex_id 
WHERE exeq.eq_id IN (1,2);

and

select ex_id, name from exercise 
where ex_id in (select ex_id from exercise_equipment where eq_id in (1,2));

But they return all exercises, instead of just the first three. In this case, if the user wants to do exercises that require no equipment or a mat (1 and 2), I want push ups, sit ups, and squats to be returned, but not the mat exercise with a ball.

I hope my explanation is clear. I would really appreciate any help I can get!


Solution

  • You want exercises that use only 1 and 2. When you use a WHERE clause, you are filtering out all the other equipment, so that won't work.

    Instead, you need to aggregate and check that no other equipment is being used for the entire exercise. This is a similar query, with a HAVING clause:

    SELECT ex.ex_id, ex.name
    FROM exercise ex LEFT JOIN
         exercise_equipment exeq
         ON ex.ex_id = exeq.ex_id 
    GROUP BY ex.ex_id, ex.name
    HAVING SUM(exeq.eq_id NOT IN (1, 2)) = 0;
    

    In the event that some exercises have no equipment at all, you might want:

    HAVING COALESCE(SUM(exeq.eq_id NOT IN (1, 2)), 0) = 0;