一对多表加入?

时间:2010-07-15 15:39:54

标签: sql sql-server database-design

我有一个表(webRooms),其中有许多行与第二个表(Residency)中的行匹配。表1如下所示:

ID  |  dorm_building  |  dorm_room |  occupant_num

表2如下所示:

student_ID  |  dorm_building  | dorm_room

我想要的是得到这样的结果:

    ID  | dorm_building  | dorm_room  | occupant_num  | student_id
    1   |  my_dorm       | 1          | 1             | 123
    2   | my_dorm        | 1          | 2             | 345

但我目前看到的是:

    ID  | dorm_building  | dorm_room  | occupant_num  | student_id
    1   |  my_dorm       | 1          | 1             | 123
    2   | my_dorm        | 1          | 2             | 123

我目前正在使用左连接,有任何建议吗?

当前查询如下所示:

select * from webRooms wR 
  LEFT JOIN RESIDENCY R on wR.dorm_building = r.DORM_BUILDING 
    and wr.dorm_room = r.DORM_ROOM 

由于给出了一些答案,我在混音中添加了第三张表。这个表已经存在 - 它是我用来生成webRooms表的,它叫做webDorms,看起来像这样:

  

ID | dorm_building | dorm_room | max_occupancy

结果如下:

  

2 | my_dorm | 1 | 2

2 个答案:

答案 0 :(得分:2)

我认为您的数据模型存在缺陷。目前,您的模型每个房间有多个记录,每个插槽一个。因为您的查询仅限制学生到房间而不是插槽,所以它会产生交叉连接,这是错误的结果。

可以克服您的查询以克服模型的缺点。在这些情况下,DISTINCT关键字是首选的工具:

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>

解决这个问题的更好方法是使用SLOTS表。这样就无需使用多个WEBROOMS记录来表示单个物理空间。你说学生被分配给Slot是“无关紧要的”,但它是成功运作学生被分配到特定Slot的关键。

以下是概念表的一些证明:

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)
/

正如您所看到的,修订后的查询清楚地显示了哪些插槽被占用以及哪些插槽仍然是免费的:

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>

或者,如果我们有一个支持PIVOT查询的数据库(我在这里使用的是Oracle 11g):

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>

答案 1 :(得分:1)

你在对APC帖子的评论中提到,你想要的只是可用性。如果确实如此,那么我认为以下是更有效的设计:

Create Table Rooms  (
                        dorm_building ... Not Null
                        , dorm_room ... Not Null
                        , capacity int Not Null default ( 0 )
                        , Constraint PK_Rooms Primary Key ( dorm_building, dorm_room )
                        , ...
                        )

Create Table Residency  (
                            student_id ... Not Null Primary Key
                            , dorm_building ... Not Null
                            , dorm_room ... Not Null
                            , Constraint FK_Residency_Rooms
                                Foreign Key ( dorm_building, dorm_room )
                                References Rooms ( dorm_building, dorm_room )
                            , ...
                            )

我只在student_id表中创建Residency主键,因为没有提及时间元素,学生不应该同时在两个房间。现在,为了获得可用空间,我们可以做到:

Select Rooms.dorm_building, Rooms.dorm_room
    , Rooms.Capacity
    , Coalesce(RoomCounts.OccupantTotal,0) As TotalOccupants
    , Rooms.Capacity - Coalesce(RoomCounts.OccupantTotal,0) As AvailableSpace
From Rooms
    Left Join   (
                Select R1.dorm_building, R1.dorm_room, Count(*) As OccupantTotal
                From Residency As R1
                Group By R1.dorm_building, R1.dorm_room
                ) As RoomCounts
        On RoomCounts.dorm_building = Rooms.dorm_building
            And RoomCounts.dorm_room = Rooms.dorm_room

现在,如果您还想显示“插槽”,那么您应该动态计算(这假设SQL Server 2005及更高版本):

With Numbers As
    (
    Select Row_Number() Over ( Order By C1.object_id ) As Value
    From sys.columns As C1
        Cross Join sys.columns As C2
    )
    , NumberedResidency As
    (
    Select dorm_building, dorm_room, student_id
        , Row_Number() Over ( Partition By dorm_building, dorm_room Order By student_id ) As OccupantNum
    From Residency
    )
Select Rooms.dorm_building, Rooms.dorm_room, R.OccupantNum, R.StudentId
From Rooms
    Join Numbers As N
        On N.Value <= Rooms.Capacity
    Left Join NumberedResidency As R
        On R.dorm_building = Rooms.dorm_building
            And R.dorm_room = Rooms.dorm_room
            And N.Value = R.OccupantNum