Why is a "references" privilege required? - sql

In order to create a FOREIGN KEY Constraint, the <AuthorizationID> that owns the referencing Table must be the current <AuthorizationID> and must have the REFERENCES Privilege on every referenced Column named. Source: https://mariadb.com/kb/en/constraint_type-foreign-key-constraint/
WHY?

The reason for the references privilege is that the table referencing another imposes a constraint on the other table, and the owner of that other table may not be willing to allow that constraint.
Let's see what could happen if there was no references privilege.
CREATE TABLE Employees
(
EmpID INTEGER NOT NULL PRIMARY KEY,
Name VARCHAR(32) NOT NULL,
…
);
INSERT INTO Employees VALUES (1, "Hernandez", …);
Now along comes Joe and creates a table:
CREATE TABLE EmployeeRefs
(
EmpID INTEGER NOT NULL PRIMARY KEY REFERENCES Employees
);
INSERT INTO EmployeeRefs SELECT EmpID FROM Employees;
Now suppose that the HR department needs to let Hernandez go and work elsewhere, and the code knows that the relevant EmpID is 1:
DELETE FROM Employees WHERE EmpID = 1;
This delete fails; the primary key value is still being referenced from the EmployeeRefs table.
This is what is supposed to happen as far as the DBMS is concerned. It was told that values in the EmployeeRefs.EmpID column must match a value in the Employee.EmpID column, and if it deletes Hernandez's record, that won't be true, so it must prevent the deletion (since Joe didn't set up a cascading delete).
Clearly, if any user (with privilege to create tables in the DB) can prevent the HR department from doing its work, there is a problem. The problem is resolved by saying that Joe can only create the reference to the Employee table if he has been given the REFERENCES privilege. And the owner/creator of each table gets to choose who has REFERENCES privilege for the tables that they create.

Related

SQL Constraint to check the value of another table's column

Suppose I have 2 tables, Employee and Sale.
Employee table has EMP_ID int as PK, and ACTIVE_STATUS bit (0 for inactive and 1 for active).
Sale table has SALE_ID as PK, EMP_ID as FK referencing Employee.EMP_ID and DATE_OF_SALE
Now, I want a constraint that checks if the EMP_ID I'm trying to insert into Sale has a value of 1 for the ACTIVE column in the Employee table, because I don't want to register a sale that is being attempted by an Inactive user.
How would I go about that? I tried CONSTRAINT CHECK_IF_ACTIVE CHECK(Employee.ACTIVE = 1) but it's not a valid statement.
You can do what you are specifically asking for using foreign keys and computed columns. First, define a redundant unique constraint in employees:
alter table employees add constraint unq_employees_empid_active_status on (empid, active_status);
Then, define a computed column in sales. Alas, this needs to be persisted, I think:
alter table sales add active_status as (convert(bit, 1)) persisted;
Then, define the foreign key constraint using both:
alter table sales add foreign key fk_sales_employees_active
foreign key (empid, active_status) references employees(empid, active_status);
Voila! The employee id can only reference active employees.
Now, you will have a problem with this -- be careful what you ask for. It is not really what you want. This enforces the constraint over all time. So, you won't be able to change the status on employees who have sales. That suggests that you need an insert trigger instead -- or a user defined function and check constraint:
create function is_employee_active (
#empid int
) returns bit as
begin
return (select active_status from employees e where e.empid = #empid);
end;
alter table sales add constraint chk_sales_employee_active
check (is_employee_active(empid) = convert(bit, 1));
Voila! This only does the check on insertion or updates. Note that once an employee is not active, you won't be able to update the row either.
You'll notice that I usually name my tables in the plural, because they contain lots of examples of an entity. My fingers just add the "s" when I'm thinking about tables.

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

update or delete on table "employee" violates foreign key constraint

I have an employee table with the following columns:
fname (varchar), lname (varchar), id (numeric)
id is the primary key.
There is a table name called works_on with columns
projectname (varchar), id (numeric)
Here, id is a foreign key that references the employee table.
When I was trying delete a row from the employee table like this:
delete from employee where id = 1
I get this error:
update or delete on table "employee" violates foreign key constraint "works_on_id_fkey" on table "works_on"`.
I am new to the database management system.
Any solution?
As employee is a foreign key in table works_on; the reason you are unable to delete employee ID 1 is because employee ID 1 exists on works_on (or perhaps other tables in which employee is a foreign key). The system is trying to maintain integrity of the database by preventing you from deleting an employee affiliated with works_on.
Say the system let you delete the employee record. Now when you look at the works_on table what would employee 1 relate to? You can no longer look up first/last names among other information. So the system is saying: If you want to delete the employee record, you must first remove/alter the foreign key associations to other system records; to ensure their continued integrity. If the system let you do this it would be called "Orphaning" a record. the parent record to which the child associates no longer exists.
To resolve a few options are:
Create a procedure that deletes employees but first checks any tables in which employee is a foreign key and ensures it's ok to delete those as well; and then deletes those records before deleting the employee record. (this can cause a massive daisy chain if those tables have PK's to which other tables are FK. But that's the nature of RDBMS.
Create a feature that lets you assign such records to employee 1's replacement or removes such records if no longer relevant.
enable ON DELETE CASCADE ON UPDATE CASCADE, which will automatically delete child records if parent record is deleted. (BE VERY CAREFUL AND CONSIDER how this impacts your system before enabling) Example: Docs
Don't delete the record, instead maintain a status field showing active/inactive and use it as a control mechanism to show or not show employees and their associated records.
There's several other options to consider as well; you must ask yourself, or the business for which this is being developed, what should happen to all those records in which employee 1 is a foreign key. Delete some/All, reassign some delete some? Prompt the user for how they want to handle each instance? Simply Inform the user they must first address the constraints found in (List all places this employee has a FK relationship?) and ensure they have a way to handle all those places... Lots of options.
You can not delete a row by that way. Because it has the constraint id in it (works_on_id_fkey). If you want to delete, you have to remove constraint from it.
alter table employee drop foreign key works_on_id_fkey

How to link three tables?

I'm new to SQL and ask for your help.
There are 3 tables, these are "Employees", "Positions" and "EmployeesPositions".
For example, 2 positions can be attached to one employee.
How to link tables so that duplicates do not occur? I read about foreign keys and JOIN, but I have not yet figured out how to do it correctly.
Table structure:
Employees (id, Name);
Positions (id, Post, Rate); EmployeesPositions - I do not know how to make it right.
What I need: when adding an employee to the "Employees" table, associate an entry with posts from the "Positions" table, but as I wrote above, one employee can be associated with 2 posts (but not always). How correctly to implement the third table (EmployeesPositions), because in Positions only posts and rates are stored, and in EmployeesPositions there should be records, for example, Name1 => Post1 and Post2, and Name2 only Post 1?
If I thought something wrong, tell me please how best to implement it.
There are several ways to solve your problem, each with their own pros and cons.
First if we simplify your problem to "an employee has zero or more positions", then you can use the following table to associate an employee with a position:
create table employeespositions (
employee_id integer not null,
position_id integer not null,
constraint pk_employeespositions
primary key (employee_id, position_id),
constraint fk_employeespositions_employee
foreign key (employee_id) references employees (id),
constraint fk_employeespositions_position
foreign key (position_id) references positions (id)
)
The foreign keys enforce the existence of the employee and the position, while the primary key ensures a combination of employee and position only exists once.
This solution has two downsides:
It does not enforce that an employee has at least one position
It allows an employee to have more than two positions
The second problem is easily fixed by adding a trigger that checks if there is at most 1 position for an employee when attempting to insert (this allows a maximum of two):
create exception tooManyPositions 'Too many positions for employee';
set term #;
recreate trigger employeespositions_bi
active before insert on employeespositions
as
declare position_count integer;
begin
select count(*)
from employeespositions
where employee_id = new.employee_id
into position_count;
if (position_count > 1) then
exception tooManyPositions;
end#
set term ;#
However this solution does not enforce that an employee has at least one position. You could add a before delete trigger that ensures that the last position cannot be deleted, but that does not ensure that a newly created employee has at least one position. If you want to enforce that, you may want to consider using stored procedures for inserting and updating employees and their positions, and have the code of those stored procedures enforce that (eg by requiring a position when creating an employee).
Alternatively, you could also consider denormalizing your design, and making the positions part of the employees record, where the employee has a 'primary' and (optionally) a 'secondary' position.
create table employees (
-- using Firebird 3 identity column, change if necessary
id integer generated by default as identity primary key,
name varchar(100),
primary_position_id integer not null,
secondary_position_id integer,
constraint fk_employees_primary_position
foreign key (primary_position_id) references positions (id),
constraint fk_employees_secondary_position
foreign key (secondary_position_id) references positions (id),
constraint chk_no_duplicate_position
check (secondary_position_id <> primary_position_id)
)
The not null constraint on primary_position_id enforces the existence of this position, while the check constraint prevents assignment of the same position to both columns. Optionally you could consider adding a before insert or update trigger that when primary_position_id is set null, will set it to the value of secondary_position_id and sets secondary_position_id to null.
This solution has the advantage of allowing the enforcement of the existence of a primary position, but may lead to additional complexities when querying positions. This disadvantage can be overcome by creating a view:
create view employeespositions
as
select id as employee_id, primary_position_id as position_id
from employees
union all
select id as employee_id, secondary_position_id as position_id
from employees
where secondary_position_id is not null;
This view can then be used as if it is a table (although you can't insert into it).

Update trigger old values natural key

I have an accounts table with the account owner as the primary key. In the update trigger, I want to update some accounts to new owners. Since this table doesn't have an id field, how do I use the inserted/updated tables in the trigger? DB is sql server 2008.
CREATE TRIGGER accounts_change_owner on accounts AFTER INSERT
AS BEGIN
MERGE INTO accounts t
USING
(
SELECT *
FROM inserted e
INNER JOIN deleted f ON
e.account_owner = f.account_owner ---this won't work since the new account owner value is diff
) d
ON (t.account_owner = d.account_owner)
WHEN MATCHED THEN
UPDATE SET t.account_owner = d.account_owner
END
I think I understood your question, but I am not sure. You want to be able update account owner name in one table and to have this update propagated to the referencing tables?
If so you don't really need a trigger, you can use on update cascade foreign key.
Like this:
create table AccountOwner
(
Name varchar(100) not null
constraint PK_AccountOwner primary key
)
create table Account
(
AccountName varchar(100) not null,
AccountOwnerName varchar(100) not null
constraint FK_Account_AccountOwnerName references AccountOwner(Name) on update cascade
)
insert AccountOwner values('Owner1')
insert Account values('Account1', 'Owner1')
Now if I update table AccountOwner like this
update AccountOwner
set Name = 'Owner2'
where Name = 'Owner1'
it will automatically update table 'Account'
select *
from Account
AccountName AccountOwnerName
----------- -----------------
Account1 Owner2
I think you need to modify the design of your table. Recall that the three attributes of a primary key are that the primary key must be
Non-null
Unique
Unchanging
(If the primary key consists of multiple columns, all columns must follow the rules above). Most databases enforce #1 and #2, but the enforcement of #3 is usually left up to the developers.
Changing a primary key value is a classic Bad Idea in a relational database. You can probably come up with a way to do it; that doesn't change the fact that it's a Bad Idea. Your best choice is to add an artificial primary key to your table, put NOT NULL and a UNIQUE constraints on the ACCOUNT_OWNER field (assuming that this is the case), and change any referencing tables to use the artificial key.
The next question is, "What's so bad about changing a primary key value?". Changing the primary key value alters the unique identifier for that particular data; if something else is counting on having the original value point back to a particular row, such as a foreign key relationship, after such a change the original value will no longer point where it's supposed to point.
Good luck.