Database structure for storing personal skills - database

I need to design a database for storing skills for a person, a person can have none,one or several skills, what is a good way to store it when it comes to easy modification of skill and fast search?
I have been thinking
1. use a bit array, each bit position represents a skill,
2. a relation table that each row link a person to a SKILL
3. each skill as a field in the table of the person
Any other suggestion or what should I aim for?

First, we need a persons table (all code examples use MySQL syntax):
CREATE TABLE IF NOT EXISTS `persons` (
`id` int unsigned NOT NULL AUTOINCREMENT,
`first_name` varchar(50) NOT NULL,
`last_name` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB Comment='Persons';
And pretend this is the data in the table:
|----|------------|-----------|
| id | first_name | last_name |
|----|------------|-----------|
| 1 | John | Doe |
| 2 | Benny | Hill |
| 3 | Linus | Torvalds |
| 4 | Donald | Knuth |
| .. | .......... | ......... |
|----|------------|-----------|
Then we need a skills table to hold all known skills:
CREATE TABLE IF NOT EXISTS `skills` (
`id` int unsigned NOT NULL AUTOINCREMENT,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB Comment='Skills';
|----|---------------|
| id | name |
|----|---------------|
| 1 | Swimming |
| 2 | Pilot |
| 3 | Writing |
| 4 | Create kernel |
| 5 | Astronaut |
| .. | ............. |
|----|---------------|
Finally we need a table that associates a person with a skill:
CREATE TABLE IF NOT EXISTS `persons_skills` (
`person_id` int unsigned NOT NULL,
`skill_id` int unsigned NOT NULL,
PRIMARY KEY (`person_id`, `skill_id`),
KEY (`person_id`),
KEY (`skill_id`)
) ENGINE=InnoDB Comment='Skills held by every person';
ALTER TABLE `persons_skills`
ADD FOREIGN KEY (`person_id`) REFERENCES `persons` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD FOREIGN KEY (`skill_id`) REFERENCES `skills` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
The primary key is defined so that no person can be associated with the same skill more than once and both columns are foreign key to their respective tables.
Assume the data below:
|-----------|----------|
| person_id | skill_id |
|-----------|----------|
| 1 | 1 |
| 2 | 1 |
| 2 | 2 |
| 3 | 1 |
| 3 | 4 |
| 4 | 2 |
| 4 | 3 |
| ......... | ........ |
|-----------|----------|
This data would indicate that John Doe, Benny Hill and Linus Torvalds all have the skill "Swimming". Benny Hill and Donald Knuth are both pilots. Linus Torvalds created a kernel. And Donald Knuth is a writer. None of the persons are an Astronaut...

It's a clasic many to many relationship so I would suggest a persons table, skills table and a personToSkill table. You other suggested solutions might be tempting at first, but they are both a maitnence hell.

Related

Postgresql 10: How to move 4 existing columns to an array column

I have the following table called client:
Table "public.client"
Column | Type | Collation | Nullable | Default
---------------------+---------+-----------+----------+------------------------------
clientid | integer | | not null | generated always as identity
account_name | text | | not null |
last_name | text | | |
first_name | text | | |
address | text | | not null |
suburbid | integer | | |
cityid | integer | | |
post_code | integer | | not null |
business_phone | text | | |
home_phone | text | | |
mobile_phone | text | | |
alternative_phone | text | | |
email | text | | |
quote_detailsid | integer | | |
invoice_typeid | integer | | |
payment_typeid | integer | | |
job_typeid | integer | | |
communicationid | integer | | |
accessid | integer | | |
difficulty_levelid | integer | | |
current_lawn_price | numeric | | |
square_meters | numeric | | |
note | text | | |
client_statusid | integer | | |
reason_for_statusid | integer | | |
Indexes:
"client_pkey" PRIMARY KEY, btree (clientid)
"account_name_check" UNIQUE CONSTRAINT, btree (account_name)
Foreign-key constraints:
"client_accessid_fkey" FOREIGN KEY (accessid) REFERENCES access(accessid)
"client_cityid_fkey" FOREIGN KEY (cityid) REFERENCES city(cityid)
"client_client_statusid_fkey" FOREIGN KEY (client_statusid) REFERENCES client_status(client_statusid)
"client_communicationid_fkey" FOREIGN KEY (communicationid) REFERENCES communication(communicationid)
"client_difficulty_levelid_fkey" FOREIGN KEY (difficulty_levelid) REFERENCES difficulty_level(difficulty_levelid)
"client_invoice_typeid_fkey" FOREIGN KEY (invoice_typeid) REFERENCES invoice_type(invoice_typeid)
"client_job_typeid_fkey" FOREIGN KEY (job_typeid) REFERENCES job_type(job_typeid)
"client_payment_typeid_fkey" FOREIGN KEY (payment_typeid) REFERENCES payment_type(payment_typeid)
"client_quote_detailsid_fkey" FOREIGN KEY (quote_detailsid) REFERENCES quote_details(quote_detailsid)
"client_reason_for_statusid_fkey" FOREIGN KEY (reason_for_statusid) REFERENCES reason_for_status(reason_for_statusid)
"client_suburbid_fkey" FOREIGN KEY (suburbid) REFERENCES suburb(suburbid)
Referenced by:
TABLE "work" CONSTRAINT "work_clientid_fkey" FOREIGN KEY (clientid) REFERENCES client(clientid)
I want to move all phone columns (business_phone, home_phone, mobile_phone, alternative_phone) as an array to one column called phone_numbers and get rid of the four phone_columns. Any idea how to do this safely without losing any records?
Add the array column.
ALTER TABLE client
ADD COLUMN phone_numbers text[];
Then use an UPDATE command to set the value of the array column based on the other four.
UPDATE client
SET phone_numbers = [business_phone,home_phone,mobile_phone,alternate_phone]; -- test and modify if needed
You can repeat this UPDATE as many times as it takes to get it right. Then you can safely DROP the four old columns.
ALTER TABLE client
DROP COLUMN business_phone
DROP COLUMN home_phone
DROP COLUMN mobile_phone
DROP COLUMN alternate_phone;

Delete specific history in verioned tables?

I'm using MariaDB 10.3. Is there a way to delete history for a specific record? I have a situation where once I delete a record, I'm under contract to remove all records (including historical ones).
Consider the following table:
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`email` varchar(255) DEFAULT NULL,
`start_trxid` bigint(20) unsigned GENERATED ALWAYS AS ROW START,
`end_trxid` bigint(20) unsigned GENERATED ALWAYS AS ROW END,
PRIMARY KEY (`id`,`end_trxid`),
PERIOD FOR SYSTEM_TIME (`start_trxid`, `end_trxid`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1 WITH SYSTEM VERSIONING
And consider the following commands run against that table:
insert into users (name, email) values ('cory', 'name#corycollier.com'), ('bob', 'bob#gmail.com');
UPDATE users set name='Cory' where id=1;
UPDATE users set name='Cory Collier' where id=1;
UPDATE users set name='Cory C' where id=1;
UPDATE users set name='Cory' where id=1;
That leaves me with the following history:
select * from (select * from users FOR SYSTEM_TIME BETWEEN (NOW() - INTERVAL 1 DAY) and (NOW())) as history where id=1;
+----+--------------+-----------------------------+-------------+----------------------+
| id | name | email | start_trxid | end_trxid |
+----+--------------+-----------------------------+-------------+----------------------+
| 1 | cory | corycollier#corycollier.com | 697377 | 697384 |
| 1 | Cory Collier | corycollier#corycollier.com | 697384 | 697391 |
| 1 | Cory C | corycollier#corycollier.com | 697391 | 697394 |
| 1 | Cory | corycollier#corycollier.com | 697394 | 697401 |
I don't have a way to delete history for that user. I'd like to.

How do I set 2 columns so each entry is unique against both columns?

I have a record that holds 2 license "keys" (actually GUIDs). When a request comes to our service it includes a key (GUID) in the request. I then do a query looking for a record that has this value in either the column Key1 or Key2.
The purpose of this is users will use Key1 for everything. Then they discover that Key1 has become public. So they switch to Key2 and then after 15 minutes, change the value of Key1. Now the old Key1 value is of no use.
By having the 2 keys, it allows the switch over with no downtime.
I need any key value to be unique. Not that any pair of values is unique. Not that a value in Key1 is unique in all rows for Key 1. But that a new value is unique in all rows.Key1 and rows.Key2.
Is there a way to force this in Sql Server. Or do I need to do this myself with a select before doing an insert or update?
-------------------------------------------------------------------------------------------
| LicenseId | ApiKey1 | APiKey2 |
| 1 | af53d192-7fa3-4be0-b3d4-7efe17a397b5 | 1a87cc4a-1941-4af7-aeaa-bf9690f47eef |
| 2 | 5bbc2d06-ed6f-4444-aa22-73820dd6f3f6 | c2bdd9d9-fd47-4727-83f8-02ed0e7537e1 |
| 3 | 8acfa8b4-aa4b-41a7-9d3d-b6ba1eac838e | 30c18f2d-5d89-4e5d-8e8e-2d2b647d6ab6 |
-------------------------------------------------------------------------------------------
I need to insure if I am going to create record LicenseId = 4, that if it has ApiKey2 = 'af53d192-7fa3-4be0-b3d4-7efe17a397b5', that the insert will fail because that guid is ApiKey1 for LicenseId = 1.
The most natural way to enforce this in the database is to put all keys in a single column. Eg
create table ApiKeys
(
LicenceId int,
KeyId int check (KeyId in (0,1)),
constraint pk_ApiKeys primary key (LicenceId,KeyId),
KeyGuid uniqueidentifier unique
)
Arguably having both the keys on the same row violates 1NF, and certainly your desire for uniqueness across the two column strongly suggests that they belong to a single domain.
So instead of storing ApiKey1 and ApiKey2 on the same row, you store them on two separate rows.
So instead of
---------------
| LicenseId | ApiKey1 | APiKey2 |
| 1 | af53d192-7fa3-4be0-b3d4-7efe17a397b5 | 1a87cc4a-1941-4af7-aeaa-bf9690f47eef |
| 2 | 5bbc2d06-ed6f-4444-aa22-73820dd6f3f6 | c2bdd9d9-fd47-4727-83f8-02ed0e7537e1 |
| 3 | 8acfa8b4-aa4b-41a7-9d3d-b6ba1eac838e | 30c18f2d-5d89-4e5d-8e8e-2d2b647d6ab6 |
-------------------------------------------------------------------------------------------
You would have:
----------------------------------------------------------
| LicenseId | KeyId | ApiKey |
| 1 | 0 | af53d192-7fa3-4be0-b3d4-7efe17a397b5|
| 1 | 1 | 1a87cc4a-1941-4af7-aeaa-bf9690f47ee4|
| 2 | 0 | 5bbc2d06-ed6f-4444-aa22-73820dd6f3f6|
| 2 | 1 | c2bdd9d9-fd47-4727-83f8-02ed0e7537e1|
| 3 | 0 | 8acfa8b4-aa4b-41a7-9d3d-b6ba1eac838e|
| 3 | 1 | 30c18f2d-5d89-4e5d-8e8e-2d2b647d6ab6|
----------------------------------------------------------

PostgreSQL conditional join - if column is not NULL

I have a table "temp"
author | title | bibkey | Data
-----------------------------------
John | JsPaper | John2008 | 65
Kate | KsPaper | | 60
| | Data2015 | 80
From this I want to produce two tables, a 'sample_table' and a 'ref_table' like so:
sample_table:
sample_id|ref_id| data
--------------------------
1 | 1 | 65
2 | 2 | 60
3 | 3 | 80
ref_table:
ref_id | author | title | bibkey
--------------------------------------
1 | John | JsPaper | John2008
2 | Kate | KsPaper |
3 | | | Data2015
I've created both tables
CREATE TABLE ref_table ( CREATE TABLE sample_table (
ref_id serial PRIMARY KEY, sample_id serial PRIMARY KEY,
author text, ref_id integer REFERENCES ref_table(ref_id),
title text, data numeric
bibkey text );
);
And inserted the unique author,title,bibkey rows into the reference table as above. What I want to do now is do the join for the sample_table to get the ref_id's. For my insert statement i currently have:
INSERT INTO sample_table (
ref_id,data
)
SELECT ref.ref_id, t.data
FROM
temp t
LEFT JOIN
ref_table ref ON COALESCE(ref.author,'00000') = COALESCE(t.author,'00000')
AND COALESCE(ref.title,'00000') = COALESCE(t.title,'00000')
AND COALESCE(ref.bibkey,'00000') = COALESCE(t.bibkey,'00000');
However i really want to have a conditional statement in the join, rather than all 3 like I have:
IF a bibkey exists for that row, I know it is unique, and join only on that.
If bibkey is NULL, then join on both author and title for the unique pair, and not bibkey.
Is this possible?

How to implement a flexible table

I am a novice database user (not designer). I would like to implement the following item in the postgres database.
I would like to implement a database which contain the following information
Table1
Classroom | Classroom Type | AV System | Student1 | Student2 | ... | Student36
1A | 'Square' | 1 | 'Anson' | 'Antonie'| ... | 'Zalda'
1B | 'Rectangle' | 2 | 'Allen' | 'Andy' | ... | 'Zeth'
There is another table to store the seating plan for each student, that's why I created another table
Table2
Classroom Type | Student1 Position | Student2 Position | ... | Student36 Position
'Square' | (1,1) | (1,2) | ... | (6,6)
'Rectangle' | (1,1) | (1,2) | ... | (4,9)
Table3
AV System | TV | Number of Speaker
1 | 'LCD' | 2
2 | 'Projector' | 4
The reason of this implementation is to draw a seating plan. However I don't think this is a good implementation. Therefore I would like to find another way which will give me some flexibility when I want to scale it up.
Thanks in advance.
This is not how relational databases work. In a relational database you don't repeat attributes, you create 1:N relationships. This process is called normalization and one of its main goals is to prevent duplication of data.
As far as I can tell, the following structure would do what you want:
-- a table to store all possible classroom types ("Square", "Rectangle", ...)
create table classroom_type
(
type_id integer not null primary key,
type_name varchar(20) not null,
unique (type_name)
);
-- a table to store all classrooms
create table classroom
(
room_id integer not null primary key,
room_name varchar(5) not null,
room_type integer not null references classroom_type,
unique (room_name)
);
-- a table containing all students
create table student
(
student_id integer not null primary key,
student_name varchar(100) not null
--- ... possibly more attributes like date of birth and others ....
);
-- this table stores the combinations which student has which position in which classroom
create table seating_plan
(
student_id integer not null references student,
room_id integer not null references room,
position varchar(10) not null,
primary key (student_id, room_id), -- make sure the same student is seated only once in a room
unique (room_id, position) -- make sure each position is only used once insid a room
);
I used integer for the ID columns, but most probably you might want to use serial to automatically create unique values for them.
Most probably the model needs to be extended to include a school year as well. Because student Allen might be in room 1A this year, but in 3C next year. This would be another attribute of the seat_plan table (and would be part of the primary key)
|ClassRoomTypes| | ClassRooms | | TableTypes | | Tables |
|--------------| |----------------| |----------------| |------------|
|Id |<--- |Id | |Id |<- |Id |
|Name | | |Name | |Name | |--|TableType_Id|
|--------------| ---|ClassRoomType_Id| |Size_X | |------------|
|Size_Y |
|----------------|
|ClassRoomToTables| |ClassRoomToTable_Students| | Students |
|-----------------| |-------------------------| |-------------------|
|Id |<--- |Id | |Id |
|ClassRoom_Id | |--|ClassRoomToTable_Id | OR |Name |
|Table_Id | |Student_Id | |ClassRoomToTable_Id|
|-----------------| |-------------------------| |-------------------|
Now a explaination:
A Class Room has a list of tables.
A Table has some paramenters ( eg: Student Capacity; Size_X, Size_Y etc)
A Table also it's a concept, (It's not something unique identificated, a table concept is used in many class rooms)
One or many students sit at many tables in different class rooms (ClassRoomToTable_Students table)
OR
One or many students may sit only at a table from specific class room (ClassRoomToTable_Id from Students)
You may get some inspiration from my point of view, I do not guarantee that totally fits your domain case. Success

Resources