Multi-value Table in SQL does not have enough columns - sql

For example, when I have a multi-value table that is the languages a person can speak. There are, let's say, 5 columns for storing the languages.
CREATE TABLE Languages
(
PersonID int primary key, //this is FOREIGN KEY to a Person, "REFERENCES Person(ID)" for example
Lang1 varchar(30),
Lang2 varchar(30),
Lang3 varchar(30),
Lang4 varchar(30),
Lang5 varchar(30)
)
What are the solutions if there is someone who can speak more than 5 languages?

Your design is broken and cannot handle the scenario you describe. You need to fix the design, the normal design for this situation would be:
CREATE TABLE Person
(
PersonID int identity (1,1) primary key,
Forename varchar(128),
Surname varchar(128)
-- etc etc
)
CREATE TABLE Language
(
LanguageID int primary key,
[Name] varchar(128)
-- etc etc
)
CREATE TABLE PersonLanguage
(
-- Optional to have a specific PK for a linking table - I prefer to
PersonLanguageID int identity (1,1) primary key,
PersonID int foreign key reference Person (PersonId),
LanguageID int foreign key reference Language (LanguageId)
)
Then you can add as many languages as you like e.g.
-- First insert will give ID 1
insert into Person (Forename, Surname)
values ('Bob', 'Test');
-- Insert will give IDs 1-7
insert into Language ([Name])
values ('English'), ('Spanish'), ('French'), ('German'), ('Dutch'), ('Portuguese'), ('Greek');
-- Add as many languages as you like
insert into PersonLanguage (PersonId, LanguageId)
values (1,1), (1,2), (1,3), (1,4), (1,5), (1,6), (1,7);

Related

SQL INSERT INTO with empty foreign table

I have a table with 3 values and a foreign table.
Tbl_Person
ID int PRIMARY KEY IDENTITY(1,1)
CarID int REFERENCES Tbl_Car(ID)
Name nvarchar(20)
Tbl_Car
ID int PRIMARY KEY IDENTITY(1,1)
Color nvarchar(30)
I now want to create a new Person with a new Car without caring about the Car table, sort of like this:
INSERT INTO Tbl_Person (Car, Name)
VALUES ('dont know what goes here', 'Timmy')
I'm not sure what to put inside the Car column since I just want it to exist but not care about its values just yet.
I'm sure its quite easy to find online but I have no idea how to Google for this particular problem.
You could alter the logical model to denormalize 'CarID' (remove it from 'Persons' table) and create a new table to store persons' cars called 'Person_Cars'. This avoids inserting NULL values into the model when the car is unknown. Depending on which table constraints are applied it also could allow for persons to have more than 1 car. Also, prefixing tables with 'tbl_' is not necessary. Also, both Persons and Cars seem likely to benefit from a unique constraint on the NVARCHAR column(s).
Persons
ID int not null PRIMARY KEY IDENTITY(1,1)
Name nvarchar(20) unique not null
Cars
ID int not null PRIMARY KEY IDENTITY(1,1)
Car nvarchar(30) unique not null
Person_Cars
ID int not null PRIMARY KEY IDENTITY(1,1)
PersonID int not null REFERENCES Persons(ID)
CarID int not null REFERENCES Cars(ID)
You can use NULL:
CREATE TABLE Tbl_Car (
ID int PRIMARY KEY IDENTITY(1,1),
Color nvarchar(30));
CREATE TABLE Tbl_Person (
ID int PRIMARY KEY IDENTITY(1,1),
CarID int REFERENCES Tbl_Car(ID),
Name nvarchar(20));
INSERT INTO Tbl_Person (CarID, Name) VALUES (NULL, 'Timmy');
DBFIDDLE
You have to do a logical operation. You can not just give the car an ID but it must have a name.
change Tbl_Car to :
CREATE TABLE Tbl_Car (
ID int PRIMARY KEY IDENTITY(1,1),
CarName nvarchar(30),
Color nvarchar(30));
To enter the Tbl_Person table, you must know the name of the person's car :
INSERT INTO Tbl_Person VALUES(CarID,Name)
SELECT ID,'Timmy'
FROM Tbl_Car
WHERE CarName = 'BMW'

Create enum in SQL Server

In MySQL one can create an enum as such:
USE WorldofWarcraft;
CREATE TABLE [users]
(
ID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
username varchar(255),
password varchar(255),
mail varchar (255),
rank ENUM ('Fresh meat', 'Intern','Janitor','Lieutenant','Supreme being')DEFAULT 'Fresh meat',
);
This is not possible in SQL Server, so what are the alternatives?
I've read this post
SQL Server equivalent to MySQL enum data type?
Suggesting something like
mycol VARCHAR(10) NOT NULL CHECK (mycol IN('Useful', 'Useless', 'Unknown'))
How can one get that work and create a default value?
The purpose of the enum would be able to tie it to a graphical dropdown on the site which presents the user with values and has a default value pre-specified.
It's better to properly normalize your model:
create table user_rank
(
id integer primary key, -- no identity, so you can control the values
rank varchar(20) not null unique
);
insert into user_rank (id, rank)
values
(1, 'Fresh Meat'),
(2, 'Intern'),
(3, 'Janitor'),
(4, 'Lieutenant'),
(5, 'Supreme being');
CREATE TABLE [users]
(
ID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
username varchar(255),
password varchar(255),
mail varchar (255),
rank integer not null default 1,
constraint fk_user_rank foreign key (rank) references user_rank (id)
);
The dropdown on your web site can easily be populated by querying the user_rank table.
There is no enum datatype available in SQL Server like in MySQL.
But using the CHECK constraint enum functionality can be implemented.
USE WorldofWarcraft;
CREATE TABLE [users]
(
ID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
username nvarchar(255),
password nvarchar(255),
mail nvarchar (255),
[rank] nvarchar (255) NOT NULL CHECK ([rank] IN('Fresh meat', 'Intern','Janitor','Lieutenant','Supreme being')) DEFAULT 'Fresh meat'
);

Enforcing exclusivity in table inheritance: composite foreign key vs check constraint

I am following the technique described in Jeff Smith's "Implementing Table Inheritance in SQL Server" (which seems to be the de facto approach for implementing this kind of structure). The People base table has a 1 : 0..1 relationship with its three subtype tables Students, Teachers, Parents. This is traditionally done by defining the primary key of the subtype tables as a foreign key to the base table.
To enforce exclusivity among the subtypes (preventing the same person from being simultaneously a student and a teacher), the author recommends adding PersonTypeID as a persisted computed column to each of the subtype tables, and including it in the foreign key constraint with the base table.
CREATE TABLE PersonType
(
PersonTypeID INT PRIMARY KEY,
Description VARCHAR(10)
);
INSERT INTO PersonType
VALUES (1, 'Student'),
(2, 'Teacher'),
(3, 'Parent');
CREATE TABLE People
(
PersonID INT PRIMARY KEY,
PersonTypeID INT REFERENCES PersonType (PersonTypeID),
Name VARCHAR(10),
UNIQUE (PersonID, PersonTypeID)
)
CREATE TABLE Students
(
PersonID INT PRIMARY KEY,
PersonTypeID AS 1 PERSISTED, -- student
EnrollmentDate DATETIME,
FOREIGN KEY (PersonID, PersonTypeID) REFERENCES People (PersonID, PersonTypeID)
)
CREATE TABLE Teachers
(
PersonID INT PRIMARY KEY,
PersonTypeID AS 2 PERSISTED, -- teacher
HireDate DATETIME,
FOREIGN KEY (PersonID, PersonTypeID) REFERENCES People (PersonID, PersonTypeID)
)
CREATE TABLE Parents
(
PersonID INT PRIMARY KEY,
PersonTypeID AS 3 PERSISTED, -- parents
DifficultyScore INT,
FOREIGN KEY (PersonID, PersonTypeID) REFERENCES People (PersonID, PersonTypeID)
)
However, this approach suffers from a number of issues:
It wastes an additional column of space on each of the subtype tables.
It requires an additional unique constraint on the base table. This wastes more space (since it will be implemented as a unique index) and slows down updates to the base table.
The foreign key constraint involves a check on two columns (instead of one), slowing down updates to the subtype tables.
My hypothesis is that it would be better to enforce uniqueness using check constraints through a scalar function instead. This would eliminate the wasted storage for the extra column and unique index, speed up updates to the base table, and hopefully achieve the same performance for updates to the subtype tables as the composite foreign key would.
CREATE TABLE People
(
PersonID INT PRIMARY KEY,
PersonTypeID INT REFERENCES PersonType (PersonTypeID),
Name VARCHAR(10)
)
CREATE FUNCTION GetPersonTypeID (#PersonID INT)
RETURNS INT
AS
BEGIN
RETURN
(
SELECT PersonTypeID
FROM People
WHERE PersonID = #PersonID
)
END;
CREATE TABLE Students
(
PersonID INT PRIMARY KEY REFERENCES People (PersonID)
CHECK (dbo.GetPersonTypeID(PersonID) = 1),
EnrollmentDate DATETIME
)
CREATE TABLE Teachers
(
PersonID INT PRIMARY KEY REFERENCES People (PersonID)
CHECK (dbo.GetPersonTypeID(PersonID) = 2),
HireDate DATETIME
)
CREATE TABLE Parents
(
PersonID INT PRIMARY KEY REFERENCES People (PersonID)
CHECK (dbo.GetPersonTypeID(PersonID) = 3),
DifficultyScore INT
)
Is there any reason why this approach should not be used?
The additional storage is actually minimal, if your person type column is a tinyint (so up to 255 types of people) you are still only using a single byte extra per person. So this should not be a massive factor in the decision, the main problem is that scalar udfs perform significantly worse than foreign key constraints. This has been tested and the results shown in in the article Scalar UDFs wrapped in CHECK constraints are very slow and may fail for multirow updates .
The testing is also included in this SO answer

Why isn't my Auto Increment working in SQL Server 2008?

This is my table creation:
create table Movie
(
MovieID int NOT NULL IDENTITY (1,1) PRIMARY KEY,
MovieName varchar(40) NOT NULL UNIQUE,
CurrentStock int NOT NULL,
GenreID int NOT NULL,
RatingID int NOT NULL,
Max_Inventory int NOT NULL,
Platforms char(5) NOT NULL,
Discontinued bit,
DiscontinuedDate Date
);
create table Inventory
(
InventoryID int NOT NULL IDENTITY (1,1) PRIMARY KEY,
MovieID int Not NULL,
CurrentStock int NOT NULL,
Max_Inventory int NOT NULL
);
Stored procedures:
ALTER PROCEDURE [dbo].[InsInventory]
(#CurrentStock int,
#ChangeStock int)
as
begin
insert into Inventory(Max_Inventory, CurrentStock)
Values (#ChangeStock, #CurrentStock)
select SCOPE_IDENTITY();
END
ALTER PROCEDURE [dbo].[InsMovie]
(#GenreID int,
#RatingID int,
#Platform varchar(5),
#MovieName varchar(40)
)
AS
BEGIN
Insert into Movie (RatingID, MovieName, Platforms, GenreID)
Values (#RatingID, #MovieName, #Platform, #GenreID)
select SCOPE_IDENTITY();
SET NOCOUNT ON;
END
Foreign key:
ALTER Table Inventory
ADD CONSTRAINT fk_Inventory_Movie
FOREIGN KEY (MovieID) REFERENCES Movie(MovieID)
ALTER TABLE Movie
ADD CONSTRAINT fk_Movie_RatingLookUp
FOREIGN KEY (RatingID) REFERENCES RatingLookUp(RatingID)
ALTER TABLE Movie
ADD CONSTRAINT fk_Movie_GenreLookUp
FOREIGN KEY (GenreID) REFERENCES GenreLookUp (GenreID)
When I run my code I keep getting the error in Visual Studio
MovieID cannot be null
but it should be when I insert a row. I also made sure to manually check to see if SQL Server had the IsIdentity set, which it is. So please help a confused programmer out.
You shouldn't insert NULL as a primary key to make it generate a value, you should just don't insert that value at all by not listing it among the fields to insert. As a sample;
INSERT INTO Movie (moviename, currentstock, genreid, ratingid, max_inventory, platforms)
VALUES ('name1', 1, 1, 1, 1, '1');
A simple working sample with your exact table creation.
Below statement is your problem. your table def shows MovieID not null. but in below statement you didn't provide that.
insert into Inventory(Max_Inventory, CurrentStock)
Values (#ChangeStock, #CurrentStock)
here is what you need to do.. first you need to retrieve Movie id which will be return from executing InsMovie sproc. than you need to use that Movie Id as a prameter to InsInvetory sproc call.
according to your table def movie id is required. also for Movie table you missed two column values that i have added which are required as well.
Please see blow updated stored procedures.
ALTER PROCEDURE [dbo].[InsInventory]
(
#CurrentStock int,
#ChangeStock int,
#MovieID int
)
as
begin
insert into Inventory(MovieID, Max_Inventory,CurrentStock)
Values (#MovieID, #ChangeStock, #CurrentStock)
select SCOPE_IDENTITY();
END
ALTER PROCEDURE [dbo].[InsMovie]
(
#GenreID int,
#RatingID int,
#Platform varchar(5),
#MovieName varchar(40),
#CurrentStock int,
#max_inventory int
)
AS
BEGIN
Insert into Movie (CurrentStock, max_inventory, RatingID, MovieName, Platforms,GenreID)
Values(#CurrentStock, #max_inventory, #RatingID,#MovieName, #Platform, #GenreID)
select SCOPE_IDENTITY();
SET NOCOUNT ON;
END

Multilingual database design

I'm trying to design a database schema for a multilingual application. I have so far found a sample from this address. http://fczaja.blogspot.com/2010/08/multilanguage-database-design.html
But I haven't understood this sample. Should I insert Id value on app_product first? How can I know that these values are true for ProductId on app_product_translation?
CREATE TABLE ref_language (
Code Char(2)NOT NULL,
Name Varchar(20) NOT NULL,
PRIMARY KEY (Code)
);
CREATE TABLE app_product (
Id Int IDENTITY NOT NULL,
PRIMARY KEY (Id)
);
CREATE TABLE app_product_translation (
ProductId Int NOT NULL,
LanguageCode Char(2) NOT NULL,
Description Text NOT NULL,
FOREIGN KEY (ProductId) REFERENCES app_product(Id),
FOREIGN KEY (LanguageCode) REFERENCES ref_language(Code)
);
It looks like SQLServer code, proceeding on that assumption.
Yes you must insert the app_product first. But you cannot insert the id column's value. It is assigned automatically, because it is an identity column.
Two things you can check out...to find the identity column's value after inserting.
The OUTPUT clause of the INSERT statement. It can return any values that are inserted, not just the identity column.
The ##Identity variable. (by far more traditional and popular)
declare #lastid int
insert into x values (1,2,3)
set #lastid = ##identity
insert into y values (#lastid, a, b, c)