Divyesh patel
Divyesh patel

Reputation: 997

Is a single field contain multiple foreign key relationship from different table [SQL]

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.

Upvotes: 2

Views: 655

Answers (6)

Stephen R
Stephen R

Reputation: 56

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.

Upvotes: 1

KumarHarsh
KumarHarsh

Reputation: 5094

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.

Upvotes: 1

Puya Goodarzi
Puya Goodarzi

Reputation: 21

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 .

Upvotes: 1

Farhad Rahmanifard
Farhad Rahmanifard

Reputation: 688

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.

Upvotes: 1

youcantexplainthat
youcantexplainthat

Reputation: 597

There are two ways to do this without re-doing your table schema

  1. 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.

  2. 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.

Upvotes: 1

BarneyL
BarneyL

Reputation: 1362

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.

Upvotes: 3

Related Questions