1

I got three tables. I want to query the badges table to get the badge if all the missions exists for the badge_id in the badge_requirements table.

In the case below, the badge would be returned because for badge_id = 1, all missions exist.

But if, for example, one record in the finished_missions table would not exist, then no badge would be returned.

The user_id will be supplied from the application.

table badges
+----+------+-------+
| id | name | image |
+----+------+-------+
| 1  | OHYE | path  |
+----+------+-------+
PK(id)

table badge_requirements
+------------+----------+
| mission_id | badge_id |
+------------+----------+
| 3          | 1        |
+------------+----------+
| 5          | 1        |
+------------+----------+
UNIQUE(mission_id, badge_id)
FK(mission_id, missions.id)
FK(badge_id, badges.id)

table finished_missions
+----+---------+------------+
| id | user_id | mission_id |
+----+---------+------------+
| 3  | 221     | 3          | // if any of these record doesn't exist
+----+---------+------------+
| 5  | 221     | 5          | // the badge associated with this mission would not be returned
+----+---------+------------+
PK(id)
FK(user_id, users.id)
FK(mission_id, missions.id)

EDIT Changed the missions table to finished_missions for better readability. The user id and mission id are just referencing the user and mission table.

EDIT 2 I've tried this, given from an answer:

SELECT * FROM badges b
INNER JOIN finished_missions fm ON (fm.user_id = 221)
INNER JOIN badge_requirements br ON (br.mission_id = fm.mission_id AND br.badge_id = b.id)

But it still returns the badge even if I have just one record in finished_missions table.

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228

4 Answers4

1

One method is a counting method:

select br.badge_id
from badge_requirements br
group by br.badge_id
having count(distinct mission_id) = (select count(*) from missions);

This returns the badge_id. If you want more information, join back to the badges table or use in.

And, if there are no duplicates in badge_requirements, then use count(*) instead of count(distinct).

Gordon Linoff
  • 1,242,037
  • 58
  • 646
  • 786
0
select * from badges b
inner join mission m on (m.user_id=@userid)
inner join badge_requirements br on (br.mission_id=m.mission_id and br.badge_id=b.id)

where @userid is SQL parameter.

Senad Meškin
  • 13,597
  • 4
  • 37
  • 55
  • I actually did exactly this query before asking question. The problem was that if just one mission exist, the badge is still returned. –  Feb 08 '16 at 15:10
  • no way, because badhr is connected to mission_id, and mission to user id if there is no record that badge wont be returned. for user. – Senad Meškin Feb 08 '16 at 15:25
0
select user_id, badge_id
from
    badge_requirements br on b.id = br.badge_id
    inner join
    missions m on m.id = br.mission_id
group by user_id, badge_id
having
    array_agg(distinct br.mission_id order by br.mission_id) =
    array_agg(distinct m.id order by m.id)
where user_id = 221
Clodoaldo Neto
  • 118,695
  • 26
  • 233
  • 260
0

There are many ways. This should be one:

SELECT badge_id
FROM  (  -- count missions per badge for the given user
   SELECT br.badge_id, count(*) AS ct
   FROM   finished_missions fm
   JOIN   badge_requirements br USING (mission_id)
   WHERE  fm.user_id = 221
   GROUP  BY  1
   ) u  -- count missions per badge total
JOIN (
   SELECT br.badge_id, count(*) AS ct
   FROM   badge_requirements
   ) b USING (badge_id, ct)  -- only badges with the full count of missions

In addition to the constraints you declared there should also be a, UNIQUE(user, mission_id) on finished_missions to disallow duplicate entries. Or you have to use count(DISTINCT mission_id) AS ct in the first subquery, so you can rely on the count.

And UNIQUE(mission_id, badge_id) should really be the PK - or add a NOT NULL constraint for both columns.

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • Great answer! I understand that UNIQUE(user, mission_id) is good to have, but why is PK(mission_id, badge_id) needed on the badge_requirements table? I already have a UNIQUE(mission_id, badge_id). –  Feb 08 '16 at 16:37
  • @Doodlemeat: A PK imposes NOT NULL on all involved columns - UNIQUE does not, see: http://stackoverflow.com/a/20006502/939860. You wouldn't want an entry in `badge_requirements` with `mission_id IS NULL`. That requirement would be impossible to meet. – Erwin Brandstetter Feb 08 '16 at 16:56