I think your data model is flawed. Currently your model has multiple records per Room, one per Slot. Because your query only restrict Students to Rooms not Slots it produces a cross-join, which is the wrong result.
It is possible to kludge your query to overcome the shortcomings of the model. The DISTINCT keyword is the blunt instrument of choice in these scenarios:
SQL> select *
2 from ( select DISTINCT dorm_building, dorm_room from webRooms) wR
3 LEFT JOIN residency R
4 on wR.dorm_building = r.dorm_building
5 and wr.dorm_room = r.dorm_room
6 /
DORM_BUILDING DORM_ROOM STUDENT_ID DORM_BUILDING DORM_ROOM
-------------------- ---------- ---------- -------------------- ----------
my_dorm 1 123 my_dorm 1
my_dorm 1 345 my_dorm 1
my_dorm 2
SQL>
A better way to tackle it would be with a SLOTS table. This removes the need to have multiple WEBROOMS records to represent a single physical Room. You say it is "inconsequential" which Slot a Student is assigned to, but it is key to the successful working of the application that a Student is assigned to a specific Slot.
Here are some proof of concept tables:
create table webrooms
(dorm_building varchar2(20)
, dorm_room number)
/
create table slots
(dorm_building varchar2(20)
, dorm_room number
, occupant_num number)
/
create table residency
(student_id number
, dorm_building varchar2(20)
, dorm_room number
, occupant_num number)
/
As you can see, the revised query provides clear indications of which Slots are occupied and which remains free:
SQL> select wr.*, s.occupant_num, r.student_id
2 from webrooms wr
3 INNER JOIN slots s
4 on wr.dorm_building = s.dorm_building
5 and wr.dorm_room = s.dorm_room
6 LEFT JOIN residency r
7 on s.dorm_building = r.dorm_building
8 and s.dorm_room = r.dorm_room
9 and s.occupant_num = r.occupant_num
10 order by 1, 2, 3, 4
11 /
DORM_BUILDING DORM_ROOM OCCUPANT_NUM STUDENT_ID
-------------------- ---------- ------------ ----------
my_dorm 1 1 123
my_dorm 1 2 345
my_dorm 2 1 678
my_dorm 2 2
my_dorm 2 3 890
my_dorm 3 1
my_dorm 3 2
my_dorm 3 3
my_dorm 4 1
my_dorm 4 2 666
9 rows selected.
SQL>
Or, if we have a database which supports PIVOT queries (I'm using Oracle 11g here):
SQL> select * from (
2 select wr.dorm_building||' #'||wr.dorm_room as dorm_room
3 , num_gen.num as slot_number
4 , case
5 when r.student_id is not null then r.student_id
6 when s.occupant_num is not null then 0
7 else null
8 end as occupancy
9 from webrooms wr
10 CROSS JOIN ( select rownum as num from dual connect by level <= 4) num_gen
11 LEFT JOIN slots s
12 on wr.dorm_building = s.dorm_building
13 and wr.dorm_room = s.dorm_room
14 and num_gen.num = s.occupant_num
15 LEFT JOIN residency r
16 on s.dorm_building = r.dorm_building
17 and s.dorm_room = r.dorm_room
18 and s.occupant_num = r.occupant_num
19 )
20 pivot
21 ( sum (occupancy)
22 for slot_number in ( 1, 2, 3, 4)
23 )
24 order by dorm_room
25 /
DORM_ROOM 1 2 3 4
---------- ---------- ---------- ---------- ----------
my_dorm #1 123 345
my_dorm #2 678 0 890
my_dorm #3 0 0 0
my_dorm #4 0 666
SQL>