If the number of room members are limited (say max 5), you could design the table horizontally instead of vertically and artifically make it vertically again in a view.
Room_ID Room_Label
----------- --------------------------------------------------
3 Aconcagua
1 Kilimanjaro
2 Mount Everest
Member_ID Member_GivenName Member_LastName
----------- -------------------------------------------------- --------------------------------------------------
1 Alice Smith
2 Bob Taylor
3 Cynthia Miller
4 Dan Cooper
RoomMember_ID Room_ID Member1_ID Member2_ID Member3_ID Member4_ID Member5_ID
------------- ----------- ----------- ----------- ----------- ----------- -----------
1 1 1 2 3 NULL NULL
2 2 1 2 NULL NULL NULL
3 3 1 2 3 4 NULL
And the view could look like this:
Room_ID Member_ID
----------- -----------
1 1
1 2
1 3
2 1
2 2
3 1
3 2
3 3
3 4
Then the RoomMember
table needs a unique constraint over the members
ALTER TABLE StackOverflow.RoomMember
ADD CONSTRAINT UK_RoomMember01
UNIQUE NONCLUSTERED (
Member1_ID ASC, Member2_ID ASC, Member3_ID ASC, Member4_ID ASC, Member5_ID ASC
)
and a check constraint that ensures the members can only be entered in ascending order from the left to the right with NULL
values to the right:
ALTER TABLE [StackOverflow].[RoomMember] WITH CHECK
ADD CONSTRAINT [CK_RoomMembers]
CHECK ((([Member2_ID] IS NULL OR [Member2_ID]>[Member1_ID]) AND ([Member3_ID] IS NULL OR [Member2_ID] IS NOT NULL AND [Member3_ID]>[Member2_ID]) AND ([Member4_ID] IS NULL OR [Member3_ID] IS NOT NULL AND [Member4_ID]>[Member3_ID]) AND ([Member5_ID] IS NULL OR [Member4_ID] IS NOT NULL AND [Member5_ID]>[Member4_ID])))
And that is it. It's now not possible to enter the same combination of members of another room, and the view provides still access in better normalized fashion.
Full source code here:
CREATE TABLE [StackOverflow].[Member](
[Member_ID] [int] IDENTITY(1,1) NOT NULL,
[Member_GivenName] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[Member_LastName] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
CONSTRAINT [PK_Member] PRIMARY KEY CLUSTERED
(
[Member_ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [StackOverflow].[Room](
[Room_ID] [int] IDENTITY(1,1) NOT NULL,
[Room_Label] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
CONSTRAINT [PK_Room] PRIMARY KEY CLUSTERED
(
[Room_ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [UK_Room] UNIQUE NONCLUSTERED
(
[Room_Label] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [StackOverflow].[RoomMember](
[RoomMember_ID] [int] IDENTITY(1,1) NOT NULL,
[Room_ID] [int] NOT NULL,
[Member1_ID] [int] NOT NULL,
[Member2_ID] [int] NULL,
[Member3_ID] [int] NULL,
[Member4_ID] [int] NULL,
[Member5_ID] [int] NULL,
CONSTRAINT [PK_RoomMember] PRIMARY KEY CLUSTERED
(
[RoomMember_ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [UK_RoomMember01] UNIQUE NONCLUSTERED
(
[Member1_ID] ASC,
[Member2_ID] ASC,
[Member3_ID] ASC,
[Member4_ID] ASC,
[Member5_ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [StackOverflow].[Member] ADD CONSTRAINT [DF_Member_Member_GivenName] DEFAULT (N'') FOR [Member_GivenName]
GO
ALTER TABLE [StackOverflow].[RoomMember] WITH CHECK ADD CONSTRAINT [FK_RoomMember_Member1] FOREIGN KEY([Member1_ID])
REFERENCES [StackOverflow].[Member] ([Member_ID])
GO
ALTER TABLE [StackOverflow].[RoomMember] CHECK CONSTRAINT [FK_RoomMember_Member1]
GO
ALTER TABLE [StackOverflow].[RoomMember] WITH CHECK ADD CONSTRAINT [FK_RoomMember_Member2] FOREIGN KEY([Member2_ID])
REFERENCES [StackOverflow].[Member] ([Member_ID])
GO
ALTER TABLE [StackOverflow].[RoomMember] CHECK CONSTRAINT [FK_RoomMember_Member2]
GO
ALTER TABLE [StackOverflow].[RoomMember] WITH CHECK ADD CONSTRAINT [FK_RoomMember_Member3] FOREIGN KEY([Member3_ID])
REFERENCES [StackOverflow].[Member] ([Member_ID])
GO
ALTER TABLE [StackOverflow].[RoomMember] CHECK CONSTRAINT [FK_RoomMember_Member3]
GO
ALTER TABLE [StackOverflow].[RoomMember] WITH CHECK ADD CONSTRAINT [FK_RoomMember_Member4] FOREIGN KEY([Member4_ID])
REFERENCES [StackOverflow].[Member] ([Member_ID])
GO
ALTER TABLE [StackOverflow].[RoomMember] CHECK CONSTRAINT [FK_RoomMember_Member4]
GO
ALTER TABLE [StackOverflow].[RoomMember] WITH CHECK ADD CONSTRAINT [FK_RoomMember_Member5] FOREIGN KEY([Member5_ID])
REFERENCES [StackOverflow].[Member] ([Member_ID])
GO
ALTER TABLE [StackOverflow].[RoomMember] CHECK CONSTRAINT [FK_RoomMember_Member5]
GO
ALTER TABLE [StackOverflow].[RoomMember] WITH CHECK ADD CONSTRAINT [FK_RoomMember_Room] FOREIGN KEY([Room_ID])
REFERENCES [StackOverflow].[Room] ([Room_ID])
GO
ALTER TABLE [StackOverflow].[RoomMember] CHECK CONSTRAINT [FK_RoomMember_Room]
GO
ALTER TABLE [StackOverflow].[RoomMember] WITH CHECK ADD CONSTRAINT [CK_RoomMembers] CHECK ((([Member2_ID] IS NULL OR [Member2_ID]>[Member1_ID]) AND ([Member3_ID] IS NULL OR [Member2_ID] IS NOT NULL AND [Member3_ID]>[Member2_ID]) AND ([Member4_ID] IS NULL OR [Member3_ID] IS NOT NULL AND [Member4_ID]>[Member3_ID]) AND ([Member5_ID] IS NULL OR [Member4_ID] IS NOT NULL AND [Member5_ID]>[Member4_ID])))
GO
ALTER TABLE [StackOverflow].[RoomMember] CHECK CONSTRAINT [CK_RoomMembers]
GO
CREATE VIEW [StackOverflow].[V_RoomMember]
AS
SELECT Room_ID, Member1_ID AS Member_ID FROM StackOverflow.RoomMember
UNION
SELECT Room_ID, Member2_ID AS Member_ID FROM StackOverflow.RoomMember WHERE Member2_ID IS NOT NULL
UNION
SELECT Room_ID, Member3_ID AS Member_ID FROM StackOverflow.RoomMember WHERE Member3_ID IS NOT NULL
UNION
SELECT Room_ID, Member4_ID AS Member_ID FROM StackOverflow.RoomMember WHERE Member4_ID IS NOT NULL
UNION
SELECT Room_ID, Member5_ID AS Member_ID FROM StackOverflow.RoomMember WHERE Member5_ID IS NOT NULL
GO
SET IDENTITY_INSERT [StackOverflow].[Room] ON
GO
INSERT [StackOverflow].[Room] ([Room_ID], [Room_Label]) VALUES (3, N'Aconcagua')
GO
INSERT [StackOverflow].[Room] ([Room_ID], [Room_Label]) VALUES (1, N'Kilimanjaro')
GO
INSERT [StackOverflow].[Room] ([Room_ID], [Room_Label]) VALUES (2, N'Mount Everest')
GO
SET IDENTITY_INSERT [StackOverflow].[Room] OFF
GO
SET IDENTITY_INSERT [StackOverflow].[Member] ON
GO
INSERT [StackOverflow].[Member] ([Member_ID], [Member_GivenName], [Member_LastName]) VALUES (1, N'Alice', N'Smith')
GO
INSERT [StackOverflow].[Member] ([Member_ID], [Member_GivenName], [Member_LastName]) VALUES (2, N'Bob', N'Taylor')
GO
INSERT [StackOverflow].[Member] ([Member_ID], [Member_GivenName], [Member_LastName]) VALUES (3, N'Cynthia', N'Miller')
GO
INSERT [StackOverflow].[Member] ([Member_ID], [Member_GivenName], [Member_LastName]) VALUES (4, N'Dan', N'Cooper')
GO
SET IDENTITY_INSERT [StackOverflow].[Member] OFF
GO
SET IDENTITY_INSERT [StackOverflow].[RoomMember] ON
GO
INSERT [StackOverflow].[RoomMember] ([RoomMember_ID], [Room_ID], [Member1_ID], [Member2_ID], [Member3_ID], [Member4_ID], [Member5_ID]) VALUES (1, 1, 1, 2, 3, NULL, NULL)
GO
INSERT [StackOverflow].[RoomMember] ([RoomMember_ID], [Room_ID], [Member1_ID], [Member2_ID], [Member3_ID], [Member4_ID], [Member5_ID]) VALUES (2, 2, 1, 2, NULL, NULL, NULL)
GO
INSERT [StackOverflow].[RoomMember] ([RoomMember_ID], [Room_ID], [Member1_ID], [Member2_ID], [Member3_ID], [Member4_ID], [Member5_ID]) VALUES (3, 3, 1, 2, 3, 4, NULL)
GO
SET IDENTITY_INSERT [StackOverflow].[RoomMember] OFF
GO