I'm working on an application which has different types of users i.e. students, tutors, and administrators. However, I initially set up my database to have only two tables: Users (that holds all the login information, common to all types) and Profiles (that holds all other information; though each column applies to a certain type of user).
I was thinking maybe I should have Users, and 3 separate tables for each type i.e. Students, Tutors, and Administrators. However, how do I link those three tables with Users table? I'm sure there would be a bridge table but I'm not too sure how to do that.
This should be a solid starting point for you. Define the tables which it sounds like you are ok with, then apply the relationships using the ALTER TABLE command. See below.
CREATE TABLE Users
(
ID INT IDENTITY (1,1),
PRIMARY KEY (ID),
UserName VARCHAR(20),
Password VARCHAR(20)
)
CREATE TABLE Students
(
ID INT IDENTITY (1,1),
PRIMARY KEY (ID),
UserID INT --foreign key to Users.ID column
--Other columns
)
CREATE TABLE Tutors
(
ID INT IDENTITY (1,1),
PRIMARY KEY (ID),
UserID INT --foreign key to Users.ID column
--Other columns
)
CREATE TABLE Administrators
(
ID INT IDENTITY (1,1),
PRIMARY KEY (ID),
UserID INT --foreign key to Users.ID column
--Other columns
)
--Apply foreign key relationships
ALTER TABLE Students
ADD FOREIGN KEY (UserID)
REFERENCES Users(ID)
ALTER TABLE Tutors
ADD FOREIGN KEY (UserID)
REFERENCES Users(ID)
ALTER TABLE Administrators
ADD FOREIGN KEY (UserID)
REFERENCES Users(ID)
You would add the primary key of the Users table as a foreign key to each of the 3 seperate tables.
Hope this helps.
USER_TYPE table, containing userType and userTypeID columns.
USER table, containing userID column plus login information, plus a userTypeID column, linking to USER_TYPE table.
USER_STUDENT table, containing student related columns, plus a userID column, linking to the USER table.
USER_TUTOR table, containing tutor related columns, plus a userID column, linking to the USER table.
USER_ADMINISTRATOR table, containing administrator related columns, plus a userID column, linking to the USER table.
You can JOIN 1, 2 and {3 or 4 or 5} tables as shown below, and in similar other ways:
SELECT U.*, S.*
FROM USER as U
INNER JOIN USER_TYPE AS UT ON UT.userTypeID = U.userTypeID
INNER JOIN USER_STUDENT AS US ON US.userID = U.userID
WHERE UT.userType = 'STUDENT'
Another approach is to introduce a "role" table that defines the type of roles/profiles in your system (student, admin, tutor, ...), then add a mapping table "user_role" which maps users to roles (ie John is both a student and a teacher implies two records in the "user_role" table. Role-specific information for a user can be in the tables shown as "detail" tables below.
USER
----
user_id
username
password
ROLE
----
role_id
role_name
USER_ROLE
----------
user_role_id
user_id
role_id
from_date
to_date
STUDENT_DETAIL
--------------
user_role_id
student_number
TUTOR_DETAIL
------------
user_role_id
sin
Related
I'm building an app that can assign guests to an existing user. Those guests can see some information about the users they are associated with.
It's N:M:
Users can have multiple guests associated
Guests can be associated to multiple users
Under the hood they are stored in the same table, the only difference between them is the role they have.
(My real table have more fields like password etc... but those are not relevant to this question)
I need to create a table like this:
The thing is that userId and guestId are referenced to the same column (id) of Users table.
Is this viable?
A user can only be either a guest or a host. This is why you have a role column in your user table.
Your guests table contains two users in a row, and as I mentioned in the request comments, I'd call them host_user_id and guest_user_id to make their roles obvious and indicate they are both user IDs.
The table design is okay for that. The only downside is that you could mistakenly make a host a guest and a guest a host, because the guests table is not aware of which is which, both are mere user IDs. If you want the database to guarantee consistency in this aspect, this gets a tad more complicated. Here is a design that deals with this problem:
create table users
(
id not null int,
full_name not null varchar(100),
role not null varchar(10),
created_at not null timestamp,
updated_at timestamp,
constraint pk_users primary key (id),
constraint chk_users_role check (role in 'host', 'guest'),
constraint unq_users_role unique (id, role)
);
create table guests
(
host_user_id not null int,
host_role not null varchar(10),
guest_user_id not null int,
guest_role not null varchar(10),
constraint pk_guests primary key (host_user_id, guest_user_id),
constraint fk_guests_host foreign key (host_user_id, host_role) references users (id, role),
constraint fk_guests_guest foreign key (guest_user_id, guest_role) references users (id, role),
constraint chk_guests_host_role check (host_role = 'host'),
constraint chk_guests_guest_role check (guest_role = 'guest')
);
This doesn't look as nice as before, as host_role being constantly 'host' and guest_role being constantly 'guest' looks redundant to us human readers, but it guarantess that a host_user_id really refers to a host user and a guest_user_id to a guest user.
Is this what you want?
create table users (
userId int generated always as identity primary key,
fullname varchar(255),
. . .
);
create table guests (
guestId int generated always as identity primary key,
fullname varchar(255),
. .
);
create table userGuests (
userGuestId int generated always as identity primary key,
userId int references users(userId),
guestId int references guests(guestId
);
You can then load these as:
insert into guests (fullname, . . .)
select distinct fullname
from old_users
where role = 'guest';
insert into users (fullname, . . .)
select distinct fullname
from old_users
where role = 'user';
insert into userGuests (userId, guestId)
select u.userId, g.userId
from old_users ou join
old_users og
on ou.id = og.id join
users u
on ou.fullname = u.fullname join
guests g
on og.fullname = g.fullname
where ou.role = 'user' and og.role = 'guest';
Note: This all uses generic SQL syntax, but it should give the idea.
I wouldn't know why it wouldn't work. I would change the name of the guests table though just to make it more clear that it doesn't hold the guests, but the guests relations.
I have 3 table Student,Teacher,User.
Student:
CREATE TABLE Student( id INT NOT NULL PRIMARY KEY,name VARCHAR(50) NOT NULL);
INSERT INTO [dbo].[Student]([id],[name]) VALUES(4,'Ram'),(5,'Raman');
Teacher:
CREATE TABLE Teacher( id INT NOT NULL PRIMARY KEY,name VARCHAR(50) NOT NULL);
INSERT INTO [dbo].[Student]([id],[name]) VALUES(1,'Raj'),(2,'Rahul');
User:
CREATE TABLE [dbo].[User](
id INT NOT NULL PRIMARY KEY,
user_id INT NOT NULL,
user_type CHAR(1) NOT NULL,
user_name VARCHAR(10) NOT NULL,
user_password VARCHAR(255) NOT NULL,
CONSTRAINT FOREIGN KEY (user_id) REFERENCES Student (id),
CONSTRAINT FOREIGN KEY (user_id) REFERENCES Teacher (id) );
Now I try to INSERT in User table with below query
INSERT INTO [dbo].[User] ([id] ,[user_id] ,[user_type],[user_name] ,[user_password]) VALUES (1 ,1,'S','Raj_001','********')
It gives me error for violation of foreign key due to
value of user_id is available in Teacher and not in Student
So my question is: How can I achieve that a value of user_id is present in one of those table and data should be inserted in User table.
Your table structure is flawed. A foreign key tells the database that there is definitely one and only one row on one side of the relationship. You can't have a partial match, it's all or nothing. This is before considering how you would ensure that you don't end up with the same id in both the teacher and student table.
It would be better to have two columns in your user table, one for teacher id and one for student id. In fact going further given the only extra data in both student and teacher tables is their name why not just eliminate both and store the name in the user table?
Another option to consider is that your foreign key is pointed in the wrong direction. Perhaps a better approach is reversing it to ensure each student and teacher is a user rather than that a user is either a student or a teacher.
First of all get rid of those key words from table name like [User],user_id etc.
It really is problematic and irritating.
Secondly why 2 key in [User] table,id, user_id ? It is not require.
I will keep only id or user_id.
Thirdly, knowing the real table structure or even purpose of each table help in better data modeling.
From [User] table what it appear is that id and user_type are composite primary key.
It should be. If this is true then you can't define FK constraint, as user_type is not available in either Teacher table and Student Table.
And what is appear that ,for example first data is inserted in Student or Teacher then data is inserted in User table in same Transaction.
So in all above scenario, Instead of Trigger is ideal scenario in this condition.
My script is just demo,
Create Proc spStudentInsert
as
set nocount on
set xact_abort on
begin try
begin tran
--bulk insert or single insert ,no problem
insert into Student
insert into [User]
if (##Trancount>0)
commit
end try
begin catch
if (##Trancount>0)
rollback
end catch
CREATE TRIGGER INSTEADOF_TR_I_User ON [user]
INSTEAD OF INSERT
AS
BEGIN
DECLARE #Flag BIT = 1
IF NOT EXISTS (
SELECT 1
FROM Student S
INNER JOIN inserted i ON i.id = S.id
)
SET #Flag = 0
ELSE IF NOT EXISTS (
SELECT 1
FROM Teacher T
INNER JOIN inserted i ON i.id = T.id
)
AND #Flag = 1
SET #Flag = 0
IF (#Flag = 0)
BEGIN
RAISERROR (
N'Invalid user'
,16
,1
)
RETURN
END
END
In case I am wrong about id, user_type composite PK then you can do other way,
PK of User id is FK in Student table as well as Teacher table.
Also , id are PK in their respective table.
So first you insert in User table then you insert in Student or Teacher table.
So design in this case will be,
CREATE TABLE [dbo].[User](
id INT NOT NULL ,
user_type CHAR(1) NOT NULL,
user_name VARCHAR(10) NOT NULL,
user_password VARCHAR(255) NOT NULL,
CONSTRAINT [PK_user] PRIMARY KEY (id)
)
INSERT INTO [dbo].[User] ([id] ,[user_type],[user_name] ,[user_password])
VALUES (1 ,1,'S','Ram_001','********')
--drop table [User]
--alter table [user]
-- drop constraint PK_user
CREATE TABLE Student( id INT NOT NULL PRIMARY KEY,name VARCHAR(50) NOT NULL);
ALTER TABLE Student
add CONSTRAINT FK_StudentUser FOREIGN KEY (id) REFERENCES [User] (id);
INSERT INTO [dbo].[Student]([id],[name]) VALUES(1,'Ram'),(5,'Raman');
--select * from [Student]
CREATE TABLE Teacher( id INT NOT NULL PRIMARY KEY,name VARCHAR(50) NOT NULL);
ALTER TABLE Teacher
add CONSTRAINT FK_TeacherUser FOREIGN KEY (id) REFERENCES [User] (id);
INSERT INTO [dbo].Teacher([id],[name]) VALUES(1,'Raj'),(2,'Rahul');
So what it appear from your question, I will create Instead of Trigger and go with that model.
There are two ways to do this without re-doing your table schema
Create a 4th table that contains the union of ID from Student and Teacher. Presumably, you would insert to that table whenever you insert into Student and Teacher, and then have the constraint act against that table.
Create a custom function based constraint rather than a foreign key which looks up against a union of both the student and teacher tables.
Neither of these are great/clean solutions, and as others have noted, you probably are dealing with the fact that the schema isn't ideal.
Still, if you're just modifying an existing system (and I assume this is a simplified version of what you're actually dealing with), then one of the two solutions I mentioned id easier than redoing the schema.
Your foreign key definition has some logical problems. It forces the user_id to exists in both tables. The solution here is depended on the business needs and real data.
You can create a Person table with 1-1 relation to the student and the Teacher tables and then use the Person.Id column in the foreign key definition. This solution assumes that the students' and teachers' data may change differently.
As another way (which is explained in other answers), If your student and teachers' data is similar, you can combine both tables, and difference data by one added "Type" column.
SO you want to tell the system that your User must be in one of your tables .
it's not possible in databases logic but you can write a script that have a condition (IF exist) then insert you user data
notice : you have to remove your foreign keys .
its a wrong logic !
you are telling your system that your user is a student and a teacher to !
that is absolutely wrong .
I feel like there were some excellent responses in this thread, but I'm going to take a stab at giving you a different direction. I'll try to be clear on why, and try to acknowledge your situation as I do so.
Student/Teacher Data is Often Messy
As someone with experience normalizing data sets in higher education, the issue you've run into resonated with me. Educational users could be in all three categories (Student, Teacher, and User) or just one of them, depending on the how and why the category was linked. Worse, they can enter from multiple directions and end up with multiple unlinked accounts. More mature institutions and tools have protections against this, but I still see user-created databases and ten year old 'it was temporary' solutions that cause me existential pain.
The Main Stumbling Block
Any database with tables that independently define who is a user based on different criteria have a potential point of failure.
Foreign keys was the right direction to be thinking in for this problem. You want these tables to connect and you want them to stay consistent with one another, regardless of which side of the data gets altered. We just need to add a little extra.
One Table To Rule Them All
Before I go further, I want to say that it is possible to get all of the fields you're tracking into a single table, but having multiple tables with distinct purposes is an easy way to protect against changes later.
The foreign key table must inherit the key from another table, but people often say foreign keys can't be primary keys as well. Why?
Foreign keys are not automatically unique keys in the tables they're in. If there can be multiple fields tied to that same key, the table ends up worthless.
We fix that with the Unique constraint. Applied to a foreign key field, Unique essentially makes it act as a primary key would.
Sample Method
Below is an alternative design for what you seemed to be after, creating a master list of IDs that can link across all tables. I tossed in a few minor tracking fields that can be useful for debugging.
/*Create Tables*/
CREATE TABLE ID(
USER_ID int NOT NULL PRIMARY KEY AUTO_INCREMENT,
USER_CREATED timestamp
);
CREATE TABLE USER(
USER_ID int NOT NULL UNIQUE FOREIGN KEY REFERENCES ID(USER_ID),
USER_LOGIN VARCHAR(10) NOT NULL UNIQUE,
USER_PASSWORD VARCHAR(255) NOT NULL,
USER_NAME VARCHAR(50) NOT NULL
);
CREATE TABLE PERMISSIONS(
USER_ID int NOT NULL UNIQUE FOREIGN KEY REFERENCES ID(USER_ID),
STUDENT CHAR(1),
TEACHER CHAR(1)
);
This creates a flag for student and teacher that could both be true or both be false. If you want the code to force them into only one or the other, you can still have the permissions table do a USER_TYPE field instead. I suggest a null or neither value being possible in either case if you plan to use this for any length of time. Best of luck.
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.
Let's suppose I have a table of roles, for example,
Roles
-----
ID Name
0 Salesman
1 Client
2 Manager
Let's also suppose that these roles are not mutually exclusive: that is, a person can be a salesman, a client, and a manager all at the same time.
If I have a Person table with a Role field, how am I able to reference multiple rows in the Roles table?
You introduce a new table that references both of the original tables:
CREATE TABLE PersonRoles (
PersonID int not null,
RoleID int not null,
constraint PK_PersonRoles PRIMARY KEY (PersonID,RoleID),
constraint FK_PersonRoles_Persons FOREIGN KEY (PersonID) references Person (ID),
constraint FK_PersonRoles_Roles FOREIGN KEY (RoleID) references Role (ID)
)
Assuming that the multiplicity is m:n, the above is correct. I made the assumption (not listed in your question) that more than one person can be e.g. a Salesman.
You have to create another table having (at least) two columns:
PersonId, RoleId
So you can insert (e.g.)
1, 0
1, 2
2, 1
and your Person with id=1 will be a Salesman and a Manager, while Person with id=2 will be a Client.
For this to work, you will need to make a "n-m relation". You need an extra table, eg. called "person_role", that contains foreign keys to both the other tables:
Person <===> PersonRole <===> Role
So you will have 3 tables:
Person
------------
ID
Name
etc.
Role
---------
ID
Name
PersonRole
------------
PersonID
RoleID
You also should make PersonID and RoleID into an unique composite key, to avoid any duplicates
/ Carsten
i am actually reading Doctrine Reference: One to Many, Unidirectional with Join table. but this will probably be more of a SQL quesiton. basically, this is supposed to model a one to many, unidirectional relationship. i guess from the PHP code (in that link), its such that 1 user have many phonenumbers.
the question is from the SQL, it seems like 1 user can have many phonenumbers. and 1 phonenumber can only belong to 1 user. am i right?
CREATE TABLE User (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE users_phonenumbers (
user_id INT NOT NULL,
phonenumber_id INT NOT NULL,
UNIQUE INDEX users_phonenumbers_phonenumber_id_uniq (phonenumber_id),
PRIMARY KEY(user_id,
phonenumber_id)
) ENGINE = InnoDB;
CREATE TABLE Phonenumber (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE users_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES User(id);
ALTER TABLE users_phonenumbers ADD FOREIGN KEY (phonenumber_id) REFERENCES Phonenumber(id);
can't i just simplify the database to ... below ... no need for join tables and what not?
Users (id, name)
Phonenumbers (id, user [FK], number)
Correct, these are two valid approaches to the same problem. And yes, the unique index on users_phonenumbers means that each phone number can belong to only one user.
The design is actually suboptimal.
The idea must have been that there are telephone numbers, users, and that they can be linked many-to-many. Because of the unique index on phonenumberid hoever, each number can only be assigned to one user.
Then the whole users_phonenumbers has become redundant, because they could just have added a userid column on the phonenumbers table and save themselves a join.
BAd table design if you ask me.