How to design a user/role schema in a SQL Server database? - sql-server

I want to design a user/role system:
The users have a name and a password and then the user can have several roles like Admin.
For this I created a schema like this:
Users:
CREATE TABLE [dbo].[Users]
(
[id] [int] NOT NULL,
[name] [nvarchar](50) NULL,
[password] [nvarchar](50) NULL,
CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED ([id] ASC)
)
Roles:
CREATE TABLE [dbo].[Roles]
(
[id] [int] NOT NULL,
[name] [nvarchar](50) NULL,
CONSTRAINT [PK_Roles] PRIMARY KEY CLUSTERED ([id] ASC)
)
user_roles:
CREATE TABLE [dbo].[User_Roles]
(
[id] [int] NOT NULL,
[User_id] [int] NOT NULL,
[Role_id] [int] NOT NULL,
CONSTRAINT [PK_User_Roles] PRIMARY KEY CLUSTERED ([id] ASC)
)
My question is: should I use foreign keys User_Roles.User_id -> User.Id
If yes why?

Not quite sure what you mean, but...
User_Roles should have 2 columns only User_id and Role_id
Both of these form the Primary Key
You do not need an extra id column User_Roles
User_id is a foreign key to Users.id
Role_id is a foreign key to Roles.id
Edit: Now I understand. Yes, always use foreign keys
Also...
if password is nvarchar(50), this implies plain text. This is bad.
if you have duplicate name values in Users, how do you know which user is which? Especially if they have the same password (which will happen because we meatsacks are stupid)
Edit after comment after primary key creation...
CREATE TABLE [dbo].[User_Roles]
(
[User_id] [int] NOT NULL,
[Role_id] [int] NOT NULL,
CONSTRAINT [PK_User_Roles] PRIMARY KEY CLUSTERED ([User_id], [Role_id]),
CONSTRAINT [UQ_ReversePK] UNIQUE ([Role_id], [User_id])
)

Spring Security makes this recommendation:
create table users(
username varchar_ignorecase(50) not null primary key,
password varchar_ignorecase(50) not null,
enabled boolean not null
);
create table authorities (
username varchar_ignorecase(50) not null,
authority varchar_ignorecase(50) not null,
constraint fk_authorities_users foreign key(username) references users(username)
);
create unique index ix_auth_username on authorities (username,authority);

Always use foreign keys when the data models a relation. In your sample, if you don't create the foreign keys, there is nothing preventing you (or someone else with access to the database) from mistakenly (or deliberately) deleting a role which is currently used.
Let's say you have many users and a few roles. One of the roles is called 'Admin' and is required in you application in order to do some tasks. If you don't have the foreign keys setup, there is nothing in the database to prevent someone from deleting the admin role, casuing your application to:
Probably crash since it will look for a role which is no longer in the database
If not the above, then at least no users will have the 'Admin' role, closing down the parts of the application where it is required
If, on the othe hand you HAVE setup the foreign keys, you will receive an error from the database if you try to delete a role which is currently assigned to some user (through the User_Roles table).

One of the best approach which is slightly different from #GBN as well will be:
I hope this helps you or someone else
CREATE TABLE [dbo].[UserRoles](
[roleId] [int] NOT NULL,
[userId] [int] NOT NULL,
[CreateDate] [datetime] NULL,
[CreateUser] [nvarchar](30) NULL,
[ModifyDate] [datetime] NULL,
[ModifyUser] [nvarchar](30) NULL,
CONSTRAINT [PK_User_Roles] PRIMARY KEY CLUSTERED
(
[roleId] ASC,
[userId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [UQ_ReversePK] UNIQUE NONCLUSTERED
(
[roleId] ASC,
[userId] 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 [dbo].[UserRoles] ADD CONSTRAINT [DF_UserRoles_ModifyDate] DEFAULT (getdate()) FOR [ModifyDate]
GO
ALTER TABLE [dbo].[UserRoles] WITH CHECK ADD CONSTRAINT [FK_UserRoles_roleId] FOREIGN KEY([roleId])
REFERENCES [dbo].[Roles] ([roleId])
GO
ALTER TABLE [dbo].[UserRoles] CHECK CONSTRAINT [FK_UserRoles_roleId]
GO
ALTER TABLE [dbo].[UserRoles] WITH CHECK ADD CONSTRAINT [FK_UserRoles_userId] FOREIGN KEY([userId])
REFERENCES [dbo].[Users] ([uId])
GO
ALTER TABLE [dbo].[UserRoles] CHECK CONSTRAINT [FK_UserRoles_userId]
GO

The users_roles table should contain the mapping between each user and their roles. Each user can have many roles, and each role can have many users:
TABLE users
id INTEGER NOT NULL PRIMARY KEY,
userName VARCHAR(50) NOT NULL
TABLE roles
id INTEGER NOT NULL PRIMARY KEY,
role VARCHAR(20) NOT NULL
CREATE TABLE users_roles (
userId INTEGER NOT NULL,
roleId INTEGER NOT NULL,
primary key (userId, roleId),
foreign key (userId) references users(id),
foreign key (roleId) references roles(id)
);

Related

Can SQL Server table have a foreign key to a table that resolves to many records?

Consider the following table...
CREATE TABLE [dbo].[Alerts]
(
[Id] [int] IDENTITY(1,1) NOT NULL,
[I18NMessageKey] [uniqueidentifier] NOT NULL
PRIMARY KEY CLUSTERED ([Id] ASC)
) ON [PRIMARY]
GO
and the following table...
CREATE TABLE [dbo].[I18NMessages]
(
[Id] [int] IDENTITY(1,1) NOT NULL,
[Key] [uniqueidentifier] NOT NULL,
[Culture] [nvarchar](200) NOT NULL,
[Message] [nvarchar](max) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
) ON [PRIMARY]
GO
I would like to add a foreign key constraint to table [Alerts] on the column [I18NMessageKey] to refer to many records in table [I18NMessages].
Is this possible without a third table?
The [I18NMessages] table holds the same message for the [Key] but in different languages depending on [Culture]. The relationship between [Alerts] and [I18NMessages] doesn't care about the culture. The resolution of [Culture] depends on the user at runtime.
In SQL Server, the uniqueness of the referenced key column(s) must be enforced by a primary key, unique constraint, or unique index. You need a third table with a unique I18NMessageKey column key to enforce referential integrity.
You can create a trigger and implement custom business logic

Foreign Key Relationship possible performance issues

We have a User table with a GUID aka uniqueidentifier as the PK. Nearly every other table in the database ties back to this table with 4 FK references. When I look at the DB diagram it looks like a 100 lane highway coming out of the User table because of the CreatedBy, CreatedByProxy, UpdatedBy, UpdatedByProxy foreign keys.
I was brought onto this project after it was already past inception and in production already, and with performance issues.
I was wondering if this DB pattern will cause major growing pains when the user list starts to grow. Are we going to run into more performance issues in the future because of this, or if we create an index will keeping the foreign keys cause the indexes to be huge. I just don't remember a website that I've worked on before that had the foreign keys to this extent before, and I'm worried about future proofing/fixing it. I'm really just trying to justify whether or not to keep or remove the foreign keys or modify the structure so that
User Table:
CREATE TABLE [dbo].[aspnet_Users](
[ApplicationId] [uniqueidentifier] NOT NULL,
[UserId] [uniqueidentifier] NOT NULL, -- ***** Here is the PK
[UserName] [nvarchar](256) NOT NULL,
[LoweredUserName] [nvarchar](256) NOT NULL,
[MobileAlias] [nvarchar](16) NULL,
[IsAnonymous] [bit] NOT NULL,
[LastActivityDate] [datetime] NOT NULL,
PRIMARY KEY NONCLUSTERED
(
[UserId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
One other Table that references User:
CREATE TABLE [dbo].[Email](
[EmailId] [int] IDENTITY(1,1) NOT NULL,
[PersonId] [int] NULL,
[InstitutionId] [int] NULL,
[EmailTypeId] [int] NOT NULL,
[EmailAddress] [varchar](254) NOT NULL,
[IsFlaggedImportant] [bit] NOT NULL,
[IsDistrictRecord] [bit] NOT NULL,
[IsActive] [bit] NOT NULL,
[Created] [datetime] NOT NULL,
[CreatedBy] [uniqueidentifier] NOT NULL, -- ***** FK 1
[Proxy] [uniqueidentifier] NULL, -- ***** FK 2
[Updated] [datetime] NULL,
[UpdatedBy] [uniqueidentifier] NULL, -- ***** FK 3
[UpdateProxy] [uniqueidentifier] NULL, -- ***** FK 4
CONSTRAINT [PK_Email] PRIMARY KEY CLUSTERED
(
[EmailId] 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
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[Email] ADD CONSTRAINT [DF_Email_IsPrimary] DEFAULT ((0)) FOR [IsFlaggedImportant]
GO
ALTER TABLE [dbo].[Email] ADD CONSTRAINT [DF_Email_IsDistrictRecord] DEFAULT ((0)) FOR [IsDistrictRecord]
GO
ALTER TABLE [dbo].[Email] ADD CONSTRAINT [DF_Email_IsActive] DEFAULT ((0)) FOR [IsActive]
GO
ALTER TABLE [dbo].[Email] ADD CONSTRAINT [DF_Email_Created] DEFAULT (getdate()) FOR [Created]
GO
ALTER TABLE [dbo].[Email] WITH CHECK ADD CONSTRAINT [FK_Email_CreatedByUser] FOREIGN KEY([CreatedBy])
REFERENCES [dbo].[aspnet_Users] ([UserId])
GO
ALTER TABLE [dbo].[Email] CHECK CONSTRAINT [FK_Email_CreatedByUser]
GO
ALTER TABLE [dbo].[Email] WITH CHECK ADD CONSTRAINT [FK_Email_EmailType] FOREIGN KEY([EmailTypeId])
REFERENCES [dbo].[EmailType] ([EmailTypeId])
GO
ALTER TABLE [dbo].[Email] CHECK CONSTRAINT [FK_Email_EmailType]
GO
ALTER TABLE [dbo].[Email] WITH CHECK ADD CONSTRAINT [FK_Email_Institution] FOREIGN KEY([InstitutionId])
REFERENCES [dbo].[Institution] ([InstitutionId])
GO
ALTER TABLE [dbo].[Email] CHECK CONSTRAINT [FK_Email_Institution]
GO
ALTER TABLE [dbo].[Email] WITH CHECK ADD CONSTRAINT [FK_Email_Person] FOREIGN KEY([PersonId])
REFERENCES [dbo].[Person] ([PersonId])
GO
ALTER TABLE [dbo].[Email] CHECK CONSTRAINT [FK_Email_Person]
GO
ALTER TABLE [dbo].[Email] WITH CHECK ADD CONSTRAINT [FK_Email_ProxyByUser] FOREIGN KEY([Proxy])
REFERENCES [dbo].[aspnet_Users] ([UserId])
GO
ALTER TABLE [dbo].[Email] CHECK CONSTRAINT [FK_Email_ProxyByUser]
GO
ALTER TABLE [dbo].[Email] WITH CHECK ADD CONSTRAINT [FK_Email_ProxyUpdateByUser] FOREIGN KEY([UpdateProxy])
REFERENCES [dbo].[aspnet_Users] ([UserId])
GO
ALTER TABLE [dbo].[Email] CHECK CONSTRAINT [FK_Email_ProxyUpdateByUser]
GO
ALTER TABLE [dbo].[Email] WITH CHECK ADD CONSTRAINT [FK_Email_UpdatedByUser] FOREIGN KEY([UpdatedBy])
REFERENCES [dbo].[aspnet_Users] ([UserId])
GO
ALTER TABLE [dbo].[Email] CHECK CONSTRAINT [FK_Email_UpdatedByUser]
GO
ALTER TABLE [dbo].[Email] WITH CHECK ADD CONSTRAINT [CK_Email_Person_Or_Institution] CHECK (([PersonId] IS NOT NULL AND [InstitutionId] IS NULL OR [PersonId] IS NULL AND [InstitutionId] IS NOT NULL))
GO
ALTER TABLE [dbo].[Email] CHECK CONSTRAINT [CK_Email_Person_Or_Institution]
GO
I was brought onto this project after it was already past inception
and in production already, and with performance issues.
You don't say what those performance issues are, so I'll confine my remarks to the foreign keys.
When I look at the DB diagram it looks like a 100 lane highway coming
out of the User table because of the CreatedBy, CreatedByProxy,
UpdatedBy, UpdatedByProxy foreign keys.
When I see columns like that, I have to ask whether they contain information about the business entity--a person's email address in this case--or information about the row that entity happens to inhabit.
It looks like they contain information about the row. (But I could be wrong.)
If they do contain information about the row, and they're not needed in most queries, you can move them to another table. If you move them, you have to be more careful about inserting rows into dbo.Email.
CREATE TABLE [dbo].[Email](
[EmailId] [int] IDENTITY(1,1) NOT NULL,
[PersonId] [int] NULL,
[InstitutionId] [int] NULL,
[EmailTypeId] [int] NOT NULL,
[EmailAddress] [varchar](254) NOT NULL,
[IsFlaggedImportant] [bit] NOT NULL,
[IsDistrictRecord] [bit] NOT NULL,
[IsActive] [bit] NOT NULL,
CONSTRAINT [PK_Email] PRIMARY KEY CLUSTERED ([EmailID])
);
CREATE TABLE [dbo].[Email_audit](
[EmailID] [int] PRIMARY KEY REFERENCES [Email] ([EmailID]),
[Created] [datetime] NOT NULL,
[CreatedBy] [uniqueidentifier] NOT NULL, -- ***** FK 1
[Proxy] [uniqueidentifier] NULL, -- ***** FK 2
[Updated] [datetime] NULL,
[UpdatedBy] [uniqueidentifier] NULL, -- ***** FK 3
[UpdateProxy] [uniqueidentifier] NULL -- ***** FK 4
);
These kinds of tables are commonly used to provide some kind of audit trail. Whether you can cascade deletes is application-dependent. In some apps, you need to store the email address here instead of the email id number, and use no foreign key references. (This allows deleting rows from dbo.Email while retaining some information about what's happened to the row.)
Moving these columns reduces the width of a row in dbo.Email by about 80 bytes, not counting overhead. That usually improves performance of SELECT statements in the tables they're moved from. (Narrower rows; more rows per page.)
Moving these columns complicates inserting and updating rows, though. All inserts and updates have to hit two tables.

Error while adding foreign key to composite key

I have two table in sql server:
First table Message_Child has a composite primary key (MessageId, ChildId)
Message_Child (MessageId, ChildId, Date)
Second table should contain a foreign key to Message_Child table, so I created two columns: MessageId and ChildId.
Request (RequestId, MessageId, ChildId, type)
And I created the constraint as follow:
Alter table Request
ADD FOREIGN KEY (MessageId, ChildId) REFERENCES Message_Child(MessageId, ChildId);
But I'm getting the following error:
There are no primary or candidate keys in the referenced table 'Message_Child' that match the referencing column list in the foreign key 'FK_Request_534D60F1'.
Edit
Adding the code:
Message_Child table:
CREATE TABLE [dbo].[Message_Child](
[ChildId] [int] NOT NULL,
[MessageId] [int] NOT NULL,
[Date] [datetime] NULL,
CONSTRAINT [PK_Message_Child] PRIMARY KEY CLUSTERED
(
[ChildId] ASC,
[MessageId] 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 [dbo].[Message_Child] WITH CHECK ADD CONSTRAINT [FK_Message_Child_Child_ChildId] FOREIGN KEY([ChildId])
REFERENCES [dbo].[Child] ([ChildId])
GO
ALTER TABLE [dbo].[Message_Child] CHECK CONSTRAINT [FK_Message_Child_Child_ChildId]
GO
ALTER TABLE [dbo].[Message_Child] WITH CHECK ADD CONSTRAINT [FK_Message_Child_Message_MessageId] FOREIGN KEY([MessageId])
REFERENCES [dbo].[Message] ([MessageId])
GO
ALTER TABLE [dbo].[Message_Child] CHECK CONSTRAINT [FK_Message_Child_Message_MessageId]
GO
RequestQueue table:
CREATE TABLE [dbo].[RequestQueue](
[RequestQueueId] [int] IDENTITY(1,1) NOT NULL,
[PIN] [nvarchar](max) NULL,
[MessageId] [int] NULL,
[ChildId] [int] NULL,
CONSTRAINT [PK_RequestQueue] PRIMARY KEY CLUSTERED
(
[RequestQueueId] 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 [dbo].[RequestQueue] WITH CHECK ADD CONSTRAINT [FK_RequestQueue_MessageChildId] FOREIGN KEY([RequestQueueId])
REFERENCES [dbo].[RequestQueue] ([RequestQueueId])
GO
ALTER TABLE [dbo].[RequestQueue] CHECK CONSTRAINT [FK_RequestQueue_MessageChildId]
GO
And then I added this:
Alter table [DayCareDB].[dbo].[RequestQueue]
ADD FOREIGN KEY (MessageId, ChildId) REFERENCES [DayCareDB].[dbo].[Message_Child](MessageId, ChildId);
Key order matters here. You need to use (ChildID, MessageID) IN THAT ORDER since that is the key order in your primary key definition.
Alter table [DayCareDB].[dbo].[RequestQueue]
ADD FOREIGN KEY (ChildId, MessageId)
REFERENCES [DayCareDB].[dbo].[Message_Child](ChildId, MessageId);

Composite primary key with foreign key relationships to same table

so I have a table called "Event" and I want to create another table where an Event can contain more Events from the same table. This is what I have so far.
This is the current existing table...
CREATE TABLE [dbo].[EventEvents]
(
[step_id] [uniqueidentifier] NOT NULL PRIMARY KEY,
[title] [nvarchar](200) NOT NULL,
[Enabled] [bit] NOT NULL,
)
Then this is the table I am trying to create...
CREATE TABLE [dbo].[EventEvents]
(
[EventId] [uniqueidentifier] NOT NULL,
[EventChildId] [uniqueidentifier] NOT NULL,
[Enabled] [bit] NOT NULL,
CONSTRAINT [PK_EventEvents] PRIMARY KEY ([EventId], [EventChildId]),
CONSTRAINT [FK_Event_EventChild] FOREIGN KEY ([EventId],[EventChildId]) REFERENCES [dbo].[Event] ([step_id], [step_id])
)
So both EventId and EventChildId both are foreign keys to Event - step_id as 1 event can other events as children within it. But I need both EventId and EventChildId to be composite primary keys.
How can I do this?
At the moment I get an error saying:
Duplicate columns specified in FOREIGN KEY constraint key list
Thanks
I've figured it out, Thanks anyway.
CREATE TABLE [dbo].[EventChildren]
(
[EventId] [uniqueidentifier] NOT NULL,
[EventChildId] [uniqueidentifier] NOT NULL,
[Enabled] [bit] NOT NULL,
CONSTRAINT [EventEvents] PRIMARY KEY CLUSTERED
(
[EventId] ASC,
[EventChildId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
ALTER TABLE [dbo].[EventChildren] WITH CHECK ADD CONSTRAINT [FK_EventChildren_Event] FOREIGN KEY([EventId])
REFERENCES [dbo].[Event] ([step_id])
GO
ALTER TABLE [dbo].[EventChildren] WITH CHECK ADD CONSTRAINT [FK_EventChildren_EventChild] FOREIGN KEY([EventChildId])
REFERENCES [dbo].[Event] ([step_id])
GO
ALTER TABLE [dbo].[EventChildren] CHECK CONSTRAINT [FK_EventChildren_Event]
GO
ALTER TABLE [dbo].[EventChildren] CHECK CONSTRAINT [FK_EventChildren_EventChild]
GO

How can I force one to one relationship on SQL Server 2008 or 2008 R2

Here is my scenario on SQL Server 2008 R2:
This is my first table:
CREATE TABLE [dbo].[Foos](
[FooId] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
CONSTRAINT [PK_Foos] PRIMARY KEY CLUSTERED
(
[FooId] ASC
)
) ON [PRIMARY]
This is the second table which has a relationship to Foos table:
CREATE TABLE [dbo].[Bars](
[BarId] [int] IDENTITY(1,1) NOT NULL,
[FooId] [int] NOT NULL,
[Name] [nvarchar](50) NULL,
CONSTRAINT [PK_Bars] PRIMARY KEY CLUSTERED
(
[BarId] ASC
)
) ON [PRIMARY]
Go
ALTER TABLE [dbo].[Bars] WITH CHECK ADD CONSTRAINT [FK_Bars_Foos] FOREIGN KEY([FooId])
REFERENCES [dbo].[Foos] ([FooId])
ON DELETE CASCADE
GO
But it is not one to one. What should I do to force this to be one to one relationship? Should I use check constraints?
Add a unique constraint to FooId in Bars.
However, you don't need BarID then because they have the same key. So it looks like this
CREATE TABLE [dbo].[Bars] (
[FooId] [int] NOT NULL,
[Name] [nvarchar](50) NULL,
CONSTRAINT [PK_Bars] PRIMARY KEY CLUSTERED (FooId),
CONSTRAINT [FK_Bars_Foos] FOREIGN KEY([FooId])
REFERENCES [dbo].[Foos] ([FooId])
ON DELETE CASCADE
)
GO
However again, you don't need Bars at all: it is one table...
You can keep Identity column(BarID) also. Then Unique key will help you out from this problem.
IF NOT EXISTS(SELECT OBJECT_ID from sys.objects WHERE name ='foo_bars')
alter table bars add constraint foo_bars unique(fooid)

Resources