SQL create multiple vs single "statuses tables" - sql

After reviewing many DB designs I'm still not sure what is the best approach.
I am designing a database where most of the entities have different statuses. For example I may have something like
User statuses: Active, Inactive, Disabled, etc.
Order statuses: Open, Close, Canceled;
Office statuses: Open, Close.
And I thinking in two different options.
1) Create one "status" table for every entity
CREATE TABLE UserStatus(
UserStatusID int,
Description varchar(255)
);
CREATE TABLE OrderStatus(
OrderStatusID int,
Description varchar(255)
);
2) Create a single shared status table for all the entities
CREATE TABLE Status(
StatusID int,
Description varchar(255)
);
If you could explain which option is better or the advantanges of each one I would be grateful

Multiple tables have a key advantage: You can declare proper foreign key relationships to ensure that the values are correct in the referenced tables.
A single table has a different advantage: You have all the statuses in one place. This can be quite handy if you need to do something like translate all the statuses into a different language.
In most cases, I think the first advantage outweighs the second. In come cases, however, the second can be important.

One more option - create a single shared status table for all the entities with EntityID.
Something like this:
CREATE TABLE dbo.Entity(
EntityID int NOT NULL
CONSTRAINT PK_Entity PRIMARY KEY CLUSTERED,
Name varchar(50) NOT NULL,
Description varchar(255) NULL,
)
CREATE TABLE dbo.Status(
EntityID int NOT NULL
CONSTRAINT FK_Status_Entity
FOREIGN KEY(EntityID) REFERENCES dbo.Entity (EntityID),
StatusID int NOT NULL,
Name varchar(50) NOT NULL,
Description varchar(255) NULL,
CONSTRAINT PK_Status PRIMARY KEY CLUSTERED (
EntityID ASC,
StatusID ASC)
CONSTRAINT UQ_Status_EntityID_Name UNIQUE NONCLUSTERED (
EntityID ASC,
Name ASC)
)

Related

Master child table with foreign key and primary key

I am designing a SQL Server database. For the following cases, which is the correct approach?
First approach:
Second approach:
This is how I'd do it if you want a user to only be part of a single user type:
create table UserTypes (User_Type_Id int identity(1,1) primary key
,UserDescription varchar(256))
create table Users (UserId int identity (1,1) primary key
,User_Type_Id int foreign key references UserTypes (User_Type_Id)
,FirstName varchar(64)
,LastName varchar(64)
,Email varchar(256))
Some comments...
This uses the IDENTITY property which auto increments in this example from 1 to 2+ billion
I would split the name into first and last, so you don't have to split them later and cause headaches with name normalization
I would avoid using reserved words like DESCRIPTION and USER_ID which are used by SQL Server and thus would need to be enclosed in brackets.
If you want a user to be part of multiple user types, then perhaps:
create table UserTypes (User_Type_Id int identity(1,1) primary key
,UserDescription varchar(256))
create table Users (UserId int identity (1,1) not null
,User_Type_Id int foreign key references UserTypes (User_Type_Id) not null
,FirstName varchar(64)
,LastName varchar(64)
,Email varchar(256))
alter table Users
add constraint PK_UserID_UserType PRIMARY KEY CLUSTERED(UserId, User_Type_Id)
Without knowing anything about your project, I'm going to assume the first one is wrong. First, you can't have two primary keys on a table. A primary key, or a unique clustered index, organizes the physical order of the table. You can't organize it in two ways. You can PK two columns into a composite key no problem. Secondly, even if you changed the user type ID to unique instead of a PK, that means only 1 user could exist with each type ID. As soon as you tried to make another user of the same type id, you would violate that unique constraint.
The 2nd model looks better. It assumes that there cannot be the same person with the same role/user type. But the typeID in the user table should be a FK instead of a PK.

How do I create a table whose rows reference 1 (and only 1) of 2 existing tables?

Here's my situation: I have two tables created with
CREATE DATABASE JsPracticeDb;
/* Create tables corresponding to the problems, solutions to
problems, and ratings of problems or solutions */
CREATE TABLE Problems (
id INT PRIMARY KEY NOT NULL,
prompt_code VARCHAR(3000),
test_func_code VARCHAR(3000),
test_input_code VARCHAR(3000)
);
CREATE TABLE Solutions (
id INT PRIMARY KEY NOT NULL,
problem_id INT,
solver_name VARCHAR(50),
code VARCHAR(3000),
FOREIGN KEY (problem_id) REFERENCES Problems(id) ON DELETE CASCADE,
);
and I was thinking about creating a table for rating Solutions, which I wrote as
CREATE TABLE Ratings (
id INT PRIMARY KEY NOT NULL,
solution_id INT,
stars TINYINT,
FOREIGN KEY (solution_id) REFERENCES Solutions(id) ON DELETE CASCADE
);
but then I realized I might actually want to have Problems rated as well. The "brute force" solution, as I see it, is
CREATE TABLE SolutionRatings (
id INT PRIMARY KEY NOT NULL,
solution_id INT,
stars TINYINT,
FOREIGN KEY (solution_id) REFERENCES Solutions(id) ON DELETE CASCADE
);
CREATE TABLE ProblemRatings (
id INT PRIMARY KEY NOT NULL,
problem_id INT,
stars TINYINT,
FOREIGN KEY (problem_id) REFERENCES Problems(id) ON DELETE CASCADE
);
but my programming intuition says there's a problem with the fact that I used copy-paste to write two sections of code that are almost identical. However, I can't think of any alternative solution that uses an intersection table or something like that also allows me to do a cascade delete. For example, I know I could do
CREATE TABLE RatedTables (
id TINYINT PRIMARY KEY NOT NULL,
table_name VARCHAR(9)
);
INSERT INTO RatedTables (table_name) VALUES ('Problems','Solutions');
CREATE TABLE Ratings (
id INT PRIMARY KEY NOT NULL,
rated_table_id TINYINT NOT NULL,
stars TINYINT,
FOREIGN KEY (rated_table_id) REFERENCES RatedTables(id)
);
but then how would I make it so that if a Solution with corresponding Ratings was deleted then those ratings would be too?????
You basically have two options but this is a good opportunity to go back and review your db structure.
The first option is to do something like this:
CREATE TABLE potential_link1 (
id int primary key,
...
);
CREATE TABLE potential_link2 (
id int primary key,
....
);
CREATE TABLE ratings (
id int primary key,
potential_link1 int references potential_link1(id) on delete cascade,
potential_link2 int references potential_link2(id) on delete cascade,
....
check(potential_link1 is null or potential_link2 is null),
check(potential_link2 is not null or potential_link1 is not null)
);
This works but as you can see it is a bit complex.
The second possibility is that since there are clear cases where a is dependent on the union of b and c then you may think about whether you can refactor your db structure to reflect that so you only need one table to link against.
There is nothing wrong with two tables looking so much alike. They contain different things and you won't want to select all three-star ratings no matter whether on problems or solutions for instance - you would always work with solution ratings or problem ratings.
But to have both ratings in one table is also not wrong and can be a good idea when you want ratings to behave the same, no matter whether on problem or solution (e.g. both shall have 1 to 5 stars, both can have a comment no longer then 200 chars, ...).
This could be done by simply giving the ratings table both a problem_id and a solution_id with foreign keys on the tables and fill always one or the other. With natural keys, the same would feel even more, well, natural:
problem(problem_no, data)
solution(problem_no, solution_no, data)
rating(problem_no, solution_no, data)
with rating.solution_no nullable and foreign keys on both parent tables.

Lookup tables localization

On an project where I use localization I have the following tables:
create table dbo.Posts
(
Id int identity not null primary key clustered (Id),
Created datetime not null,
);
create table dbo.PostsLocalized
(
Id int identity not null primary key clustered (Id),
PostId int not null,
LanguageId int not null,
PostTypeId int not null,
[Text] nvarchar (max) not null,
Title nvarchar (120) not null,
constraint UQ_PostsLocalized_PostId_LanguageId unique (PostId, LanguageId)
);
create table dbo.PostTypes
(
Id int identity not null primary key clustered (Id),
Name nvarchar (max) not null
);
So I am localizing the Posts with a PostsLocalized table but not the PostTypes table.
The PostTypes table is basically a lookup table as others I have in my database.
Do you think I should localize the lookup tables, for example, PostTypes?
I would add a new table named PostTypesLocalized with the localized names.
The same for other lookup tables like Genders, Countries, ...
Or should I localize the lookup tables only in the application?
UPDATE
To clarify:
All localized versions of one post has the same PostType.
I need to display the PostTypes in the UI that is why I need to translate them.
So I tried a new approach following the answer of #dasblinkenlight:
create table dbo.Posts
(
Id int identity not null primary key clustered (Id), -- The id of the localized post
Created datetime not null,
PostId int not null, -- The id of the post
PostTypeId int not null
LanguageId int not null,
[Text] nvarchar (max) not null,
Title nvarchar (120) not null,
constraint UQ_PostsLocalized_PostId_LanguageId unique (PostId, LanguageId)
);
create table dbo.PostTypes
(
Id int identity not null primary key clustered (Id), -- PostType localized id
PostTypeId int not null, -- The id of the post type
Name nvarchar (max) not null
);
Considering (1) then Posts > PostTypeId should be related to PostTypes > PostTypeId.
But how can I do this?
The answer depends on the usage of the Name field of the PostTypes table:
If all uses of that field come from code and/or non-localizable scripts that you may have, localization is not necessary
If the Name makes it to the end-user's view, you should localize the table.
If you need to localize PostTypes, a separate PostTypesLocalized table, in addition to the PostTypes table with locale-independent name, sounds like an appropriate solution.
You should consider the placement of the PostTypeId field, too. Would all localizations with the same PostId refer to the same PostTypeId, or would some of them be different? In case that all localizations of the same Post refer to the same PostType, the field should belong to the Posts table, instead of PostLocalized.
should I localize the lookup tables only in the application?
Adding localization to your database counts as localization of your application. It is a good solution when you contemplate multiple applications using the same database structure.

Data historicization

during the phase of collection and analysis of requirements, the users of the application told me that many concept should be historicized. Then I'm thinking to implement it in this way:
Suppose to have to historicized a table called user details,
create table user_details(
id int,
mail varchar(50),
telephone varchar(50),
fax varchar(50),
dateFrom date,
dateTo date,
primary key(id,dateFrom)
);
with the last two fields I think to manage the historicization of this entity.
Any suggestions about this?
Is this the better way to manage it?
It might work, but problem nowadays is that DBMS's do not generally enforce temporal primary key constraints or temporal referential integrity constraints.
I would do like this (in T-SQL syntax):
CREATE TABLE USER
(
id_user int not null identity (1,1),
natural_key varchar(50) not null
)
;
ALTER TABLE USER
ADD CONSTRAINT [XPK_user_iduser]
PRIMARY KEY CLUSTERED (id_user ASC)
GO
ALTER TABLE USER
ADD CONSTRAINT [XAK1_user_naturalkey]
UNIQUE (natural_key ASC)
GO
CREATE TABLE USER_DETAIL
(
id_user int not null,
mail varchar(50),
telephone varchar(50),
fax varchar(50),
dateFrom date not null,
dateTo date
)
;
ALTER TABLE USER_DETAIL
ADD CONSTRAINT [XPK_userdetail_1]
PRIMARY KEY CLUSTERED (id_user ASC, dateFrom ASC)
GO
And finally RI here:
ALTER TABLE USER_DETAIL
ADD CONSTRAINT [XFK_userdetail_user_1]
FOREIGN KEY (id_user) REFERENCES USER (id_user)
ON DELETE NO ACTION
ON UPDATE NO ACTION
GO
This construct does not help stopping all anomalities but at least there is no possibility for two tuples having same starting time.
Of course you could create a table USER_DETAIL and USER_DETAIL_HIST and latter would contain values from earlier periods. Your USER_DETAIL table could contain only current records.
I would then create a following view for end-user applications:
CREATE VIEW USER_DETAIL_TOT AS
SELECT id_user,mail,telephone,fax,dateFrom,dateTo,'Current' as rowStatus
FROM USER_DETAIL
UNION ALL
SELECT id_user,mail,telephone,fax,dateFrom,dateTo,'Historical' as rowStatus
FROM USER_DETAIL_HIST
GO

SQL Server - Create an identifying relationship

I'm currently designing a database to be implemented in SQL Server. I created the following tables without problem:
CREATE TABLE [Client] (
[ClientId] INT NOT NULL,
[Name] VARCHAR(45) NOT NULL,
[IsEnabled] BIT NOT NULL DEFAULT 1,
CONSTRAINT PK_TCASystem PRIMARY KEY CLUSTERED (
ClientId
)
);
CREATE TABLE [Configuration] (
[ConfigId] INT NOT NULL,
[ClientId] INT NOT NULL,
[Name] VARCHAR(45) NOT NULL,
CONSTRAINT PK_Configuration PRIMARY KEY CLUSTERED (
ConfigId, ClientId
),
CONSTRAINT "FK_SystemConfiguration" FOREIGN KEY
(
ClientId
) REFERENCES [Client] (
ClientId
)
);
However, when I tried to add this one:
CREATE TABLE [Mail] (
[MailId] INT NOT NULL,
[ConfigId] INT NOT NULL,
[Recipient] VARCHAR(500) NOT NULL,
[Sender] VARCHAR(50) NOT NULL,
[Subject] VARCHAR(250) NOT NULL,
[Message] TEXT NULL,
CONSTRAINT PK_Mail PRIMARY KEY CLUSTERED (
MailId, ConfigId
),
CONSTRAINT "FK_ConfigurationMail" FOREIGN KEY
(
ConfigId
) REFERENCES [Configuration] (
ConfigId
)
);
I got an error saying that There are no primary or candidate keys in the referenced table 'Configuration' that match the referencing column list in the foreign key 'FK_ConfigurationMail'. I believe this is because the constraint is trying to reference ConfigId, only one half of the composite key, and for this to work I'd need to reference the ClientId too, is that correct?
But my problem is that I first did the design for this database in MYSQL Workbench, and there I indicated that Configuration and Mail, as well as Client and Configuration, have a 1:n identifying relationship (because a Mail instance cannot be created if there isn't a Configuration instance first, and at the same time a Configuration instance cannot exist without having being assigned to a Client first), and as such it created the composite keys for Configuration and Mail. You can see a picture of that here.
So my question is, how can I translate this identifying relationship to SQL Server? Or is that not possible?
EDIT: As suggested I will remove the composite keys from the Configuration table, albeit my question still stands: If I have a 1:n identifying relationship where one of the tables involved uses composite keys, how can I display this on SQL Server? Or is such a case never supposed to happen?
2ND EDIT: To anyone who might come across this question, this post is well worth a read. Cleared up all my confusion in the matter.
Foreign key must reference PK (the entire PK, not portion of PK) or unique index. So add this between create table [Configuration] and [Mail].
CREATE UNIQUE NONCLUSTERED INDEX [UX_Configuration] ON [Configuration]
(
[ConfigId] ASC
)
Check out at sql fiddle for the whole working script:
http://www.sqlfiddle.com/#!3/8877f