SQL one-to-many - sql

I am trying to build an SQL schema for a system where we have channels, each with an id, and one or more fixtures. I am having difficulty finding a way to implement this one-to-many mapping. (i.e. One channel to many fixtures). I am using the H2 database engine.
I cannot have a table :
id | fixture
----|----------
1 | 1
1 | 2
2 | 3
CREATE TABLE channel(
id INT NOT NULL PRIMARY KEY,
fixture INT NOT NULL
);
... as the PRIMARY KEY id must be UNIQUE.
Similarly, I cannot map as follows:
CREATE TABLE channel(
id INT NOT NULL PRIMARY KEY,
f_set INT NOT NULL REFERENCES fixtures(f_set)
);
CREATE TABLE fixtures(
id INT NOT NULL PRIMARY KEY,
f_set INT NOT NULL
);
... as this required f_set to be UNIQUE
I am currently implementing it as follows:
CREATE TABLE channel(
id INT NOT NULL PRIMARY KEY,
f_set INT NOT NULL REFERENCES fixture_set(id)
);
CREATE TABLE fixtures(
id INT NOT NULL PRIMARY KEY,
f_set INT NOT NULL REFERENCES fixture_set(id)
);
CREATE TABLE fixture_set(
id INT NOT NULL PRIMARY KEY
);
... but this means that we can have a channel with a fixture_set which does not have any assigned fixtures (Not ideal).
I was wondering if you had any suggestions for how i may approach this (Or where my understanding is wrong). Thanks

"One-to-many" means that many items (may) reference one item. If it's one channel to many fixtures, then fixtures should reference channels, not the other way round, which means the reference column should be in the fixtures table:
CREATE TABLE channel(
id INT NOT NULL PRIMARY KEY
);
CREATE TABLE fixtures(
id INT NOT NULL PRIMARY KEY,
channel_id INT NOT NULL FOREIGN KEY REFERENCES channel (id)
);

You can add a CONSTRAINT just to check it.
Sorry for not pasting a snippet... I don't know anything about H2 specifics.
Or you could also avoid the fixture-set concept at all.
Then you would just need:
channel table, with just the id (plus other fields not involved on that matter, of course)
a channelfixtures table, with channelId and fixtureId. Primary key would be composed of (channelId, fixtureId)
a fixture table, only if you need it.

Related

Foreign key to table with composite primary key

We are making a translation system and we're struggling finding the best way to model our database.
What we have right now is:
CREATE TABLE Translation
(
id INT NOT NULL PRIMARY KEY
EN VARCHAR(MAX) NULL
DE VARCHAR(MAX) NULL
FR VARCHAR(MAX) NULL
...
);
This solution combines all translations to one entry. Downside is that if you have to add a language, you have to add a column. The upside is that you have a primary key which can be used for foreign keys.
Alternative solution:
CREATE TABLE TranslationId
(
id INT NOT NULL PRIMARY KEY
);
CREATE TABLE Translation
(
id INT NOT NULL
Language VARCHAR(2) NOT NULL
Translation VARCHAR(MAX) NULL
);
id in Translation has a foreign key to the id of TranslationId (and is not unique in the Translation table). This solution doesn't have the disadvantage of the first solution. The disadvantage is that this may be overengineered. To get all the translations for a certain id, you need to pass through an extra table.
Both solutions will work. Any thoughts on either solution?

Is there a way to create a conditional foreign key depending on a field's value?

Trying to create a table like
CREATE TABLE SearchUser(
SearchID int NOT NULL PRIMARY KEY,
UserID int FOREIGN KEY REFERENCES Users(UserId),
[scan_id] NVARCHAR(8),
[scan_type] NVARCHAR(3),
[tokens_left] INT);
where depending on scan_type value, I would like to be able to utilize different foreign keys
i.e.
if [scan_type] = 'ORG'
I would like [scan_id] to be a foreign key to Org(scan_id)
if [scan_type] = 'PER'
I would like [scan_id] to be a foreign key to Per(scan_id)
In SQL Server it's impossible to create a dynamic foreign key but, you can implement table inheritance, which solves your problem e.g.:
CREATE TABLE BaseScan(Id INT PRIMARY KEY,SharedProperties....);
CREATE TABLE OrgScan(
Id INT...,
BaseScanId INT NOT NULL FOREIGN KEY REFERENCES BaseScan(Id));
CREATE TABLE dbo.PerScan(
Id INT...,
BaseScanId INT NOT NULL FOREIGN KEY REFERENCES BaseScan(Id));
This way you'll be able to reference BaseScan.Id in SearchUser and then join the data you need depending on 'scan-type' value.
As other devs mention, it is not possible directly in nutshell. But there is still hope if you want to implement the same.
You can apply check constraint on column. You can create function for check and call it for check constraint like below.
ALTER TABLE YourTable
ADD CONSTRAINT chk_CheckFunction CHECK ( dbo.CheckFunction() = 1 );
In function you can write your logic accordingly.
CREATE FUNCTION dbo.CheckFunction ()
RETURNS int
AS
BEGIN
RETURN ( SELECT 1 );
END;

Database - Am i doing this right?

I'm trying to make a simple database for a personal project, and I'm not sure whether i'm using Primary Keys properly.
Basically, the database contains users who have votes yes/no on many different items.
Example :
User "JOHN" voted YES on item_1 and item_2, but voted FALSE on item_3.
User "BOB" voted YES on item_1 and item_6.
User "PAUL" votes NO on item_55 and item_76 and item_45.
I want to use the following 3 tables (PK means Primary Key) :
1) table_users, which contains the columns "PK_userID" and "name"
2) table_items, which contains the columns "PK_itemID" and "item_name"
3) table_votes, which contains the columns "PK_userID", "PK_itemID", and "vote"
and the columns with the same name will be linked
Does it look like a proper way to use primary keys ? (so the table_votes will have two Primary Keys, being linked to the two other tables)
Thanks :)
Since user can vote for multiple items and multiple users can vote for a single item, you should not create following two primary keys in third table table_votes. Just create them as fields otherwise it will restrict you add only a userId or itemId only once. Yep, you should make them NOT NULL
"PK_userID", "PK_itemID",
No, that's not correct.
There can only be one primary key per table. You can have other columns with unique indexes that could have been candidate keys, but that's not the primary.
I think you'd have three tables:
create table PERSON (
PERSON_ID int not null identity,
primary key(PERSON_ID)
);
create table ITEM (
ITEM_ID int not null identity,
primary key(ITEM_ID)
);
create table VOTE (
PERSON_ID int,
ITEM_ID int,
primary key(PERSON_ID, ITEM_ID),
foreign key(PERSON_ID) references PERSON(PERSON_ID),
foreign key(ITEM_ID) references ITEM(ITEM_ID)
);
It's a matter of cardinality. A person can vote on many items; an item can be voted on by many persons.
select p.PERSON_ID, i.ITEM_ID, COUNT(*) as vote_count
from PERSON as p
join VOTE as v
on p.PERSON_ID = v.PERSON_ID
join ITEM as i
on i.ITEM_ID = v.PERSON_ID
group by p.PERSON_ID, i.ITEM_ID
This looks reasonable. However, I would not advise you to name your primary keys with a "PK_" prefix. This can be confusing, especially because I advise giving foreign keys and primary keys the same name (the relationship is then obvious). Instead, just name it after the table with Id as a suffix. I would recommend a table structure such as this:
create table Users (
UserId int not null auto_increment primary key,
Name varchar(255) -- Note: you probably want this to be unique
);
create table Items (
ItemId int not null auto_increment primary key,
ItemName varchar(255) -- Note: you probably want this to be unique
);
create table Votes (
UserId int not null references Users(UserId),
ItemId int not null references Items(ItemId),
Votes int,
constraint pk_UserId_ItemId primary key (UserId, ItemId)
);
Actually, I would be inclined to have an auto-incremented primary key in Votes, with UserId, ItemId declared as unique. However, there are good arguments for doing this either way, so that is more a matter of preference.

Inheritance in SQL. How to guarantee it?

I am trying to create inheritance as in a C# object using SQL Server and I have:
create table dbo.Evaluations
(
Id int not null constraint primary key clustered (Id),
Created datetime not null
);
create table dbo.Exams
(
Id int not null,
Value int not null
// Some other fields
);
create table dbo.Tests
(
Id int not null,
Order int not null
// Some other fields
);
alter table dbo.Exams
add constraint FK_Exams_Id foreign key (Id) references dbo.Evaluations(Id);
alter table dbo.Tests
add constraint FK_Tests_Id foreign key (Id) references dbo.Evaluations(Id);
Which would translate to:
public class Evaluation {}
public class Exam : Evaluation {}
public class Test : Evaluation {}
I think this is the way to go but I have a problem:
How to force that an Evaluation has only one Test or one Exam but not both?
To find which type of evaluation I have I can check exam or test for null. But should I have an EvaluationType in Evaluations table instead?
NOTE:
In reality I have 4 subtypes each one with around 40 to 60 different columns.
And in Evaluations table I have around 20 common columns which are also the ones which i use more often to query so I get lists.
First, don't use reserved words such as order for column names.
You have a couple of choices on what to do. For this simple example, I would suggest just having the two foreign key references in the evaluation table, along with some constraints and computed columns. Something like this:
create table dbo.Evaluations
(
EvaluationId int not null constraint primary key clustered (Id),
ExamId int references exams(ExamId),
TestId int references tests(TestId),
Created datetime not null,
EvaluationType as (case when ExamId is not null then 'Exam' when TestId is not null then 'Test' end),
check (not (ExamId is not null and TestId is not null))
);
This approach gets less practical if you have lots of subtypes. For your case, though, it provides the following:
Foreign key references to the subtables.
A column specifying the type.
A validation that at most one type is set for each evaluation.
It does have a slight overhead of storing the extra, unused id, but that is a small overhead.
EDIT:
With four subtypes, you can go in the other direction of having a single reference and type in the parent table and then using conditional columns and indexes to enforce the constraints:
create table dbo.Evaluations
(
EvaluationId int not null constraint primary key clustered (Id),
EvaluationType varchar(255) not null,
ChildId int not null,
CreatedAt datetime not null,
EvaluationType as (case when ExamId is not null then 'Exam' when TestId is not null then 'Test' end),
ExamId as (case when EvaluationType = 'Exam' then ChildId end),
TestId as (case when EvaluationType = 'Test' then ChildId end),
Other1Id as (case when EvaluationType = 'Other1' then ChildId end),
Other2Id as (case when EvaluationType = 'Other2' then ChildId end),
Foreign Key (ExamId) int references exams(ExamId),
Foreign Key (TestId) int references tests(TestId),
Foreign Key (Other1Id) int references other1(Other1Id),
Foreign Key (Other2Id) int references other2(Other2Id)
);
In some ways, this is the better solution to the problem. It minimizes storage and is extensible for additional types. Note that it is using computed columns for the foreign key references, so it is still maintaining relational integrity.
My best experience is include all columns in one table.
Relation model is not much friendly with object oriented design.
If you treat every class as one table, you can get performance problems with high number of rows in "base-table" (base class) or you can suffer from a lot of joins if you have level of inheritance.
If you want minimalize amount of work to get correct structure, create your own tool, which can genrate create/alter scripts of tables for chosen classes. It's in fact pretty easy. Then you can generate also your data access layer. In result you will get automatic worker and you can focus on complex tasks and delegate work for "trained monkeys" to computer not humans.

Best approach cascade deleting related entity in MS SQL

Need advice of the best approach how to design DB for the following scenario:
Following below DB structure exmaple (it's not real just explain problem)
File
(
Id INT PRIMARY KEY...,
Name VARCHAR(),
TypeId SMALLINT,
...
/*other common fields*/
)
FileContent
(
Id INT PRIMARY KEY...,
FileId FOREIGN KEY REFERENCES File(Id) NOT NULL ON DELETE CASCADE UNIQUE,
Content VARBINARY(MAX) NOT NULL,
)
Book
(
Id INT PRIMARY KEY...,
Name VARCHAR(255),
Author VARCHAR(255)
...
CoverImageId FK REFERENCES File(Id),
)
BookPageType
(
Id TINYINT PRIMARY KEY...,
Name VARCHAR(50),
)
BookPage
(
Id INT PRIMARY KEY...,
TypyId TINYINT FOREIGN KEY REFERENCES BookPageType(Id),
BookId INT FOREIGN KEY REFERENCES Book(Id) ON DELETE CASCADE,
Name VARCHAR(100),
CreatedDate DATETIME2,
...
/*other common fields*/
)
BookPage1
(
Id PRIMARAY KEY REFERENCES BookPage(Id) NOT NULL ON DELETE CASCADE,
FileId PRIMARAY KEY REFERENCES File(Id)
...
/* other specific fileds */
)
...
BookPageN
(
Id PRIMARAY KEY REFERENCES BookPage(Id) NOT NULL ON DELETE CASCADE,
ImageId PRIMARAY KEY REFERENCES File(Id),
...
/* other specific fileds */
)
Now question is I want to delete Book with all pages and data (and it works good with delete cascade), but how to make cascade delete the associated files also (1 to 1 relentionship).
Here I see following approaches:
Add file to every table when I use it, but I don't want to copy file
schema for every table
Add foreign keys to the File table (instead of page for example), but since I use file for e.g. in 10 tables I will have 10 foreign keys in file table. This also not good
Use triggers, what I don't wnat to do
Thanks in Advance
If such necessary is appeared maybe it seems you need refactor your base.
You said this example is not real and I'll not ask about N tables for pages though it's strange. If not all files have 1 to 1 relationship and so you need remove only a file that other book does not refer to, it's sounds like a job for a trigger.
So what you have defined is a many-to-many relationship between BookPage and File. this is a result of the one-to-many relationship between BookPage and BookPageN and then the one-to-many relationship between File and BookPageN. To get the relationships you say you want in the text, you need to turn the relationship around to point from BookPageN to File. Maybe instead of having so many BookPageN tables you could find a way to consolidate them into a single table. Maybe just use the BookPage table. Just allow nulls for the fields that are optional.