Query regarding SQLite and Cascading foreign keys - sql

I am currently writing a application in C# that uses a SQLite database to store information the user will input. The application is basically a Management system for users who are called "Students" in the application. This is the most important table in my database and every other table is linked off this table. What I want to do is when a student is removed - they leave the institute/get kicked out etc. - is to remove their data from all the other tables so that data is no longer there as it isn't needed. An example of some of the Create table statements I have written is:
CREATE TABLE student(studentID int(5) PRIMARY KEY NOT NULL, name string(16),...,DOB string(8) );
CREATE TABLE emergencyContact(emergencyID int(5) PRIMARY KEY NOT NULL, name string(16),..., contactNumber int(16));
CREATE TABLE emergencyContactOf(studentID int(5) FOREIGN KEY REFERENCES student('studentID'), emergencyID int(5) FOREIGN KEY REFERENCES emergencyContact('emergencyID');
I have read up on this and my understanding is the data will be deleted in the EmergencyContactOf table if I include a 'ON DELETE CASCADE' statement as the StudentID key will no longer be present in the Parent table.
However, my understanding is the data in the EmergencyContact table that is for that specific student will not be deleted as there is no reference to the StudentID.
My question is, is there a way to remove the data from this table also that is relevant to that Student? For example, if I was to include a column in the EmergencyContact table which would reference the StudentID as a Foreign Key and then remove that row if the StudentID is ever deleted from the parent table? Is this a good solution to this particular problem?
All other tables I have are also designed in this way, where the data is in different tables and then linked back to the Student table with relationship tables so this will also apply to all the other tables I have.
Thanks.

My question is, is there a way to remove the data from this table also that is relevant to that Student? For example, if I was to include a column in the EmergencyContact table which would reference the StudentID as a Foreign Key and then remove that row if the StudentID is ever deleted from the parent table? Is this a good solution to this particular problem?
What happens if multiple students have the same emergency contact? You don't want to duplicate data if you don't have to - that's the whole point of the emergencyContactOf table, to efficiently set up a many to many relation between students and emergency contacts. So you don't want to do something like you describe.
You could periodically (Monthly, yearly, after purging student rosters, whatever) run a delete that removes rows from emergencyContact if they don't appear in emergencyContactOf:
DELETE FROM emergencyContact
WHERE emergencyID NOT IN (SELECT emergencyID FROM emergencyContactOf)
or the like.

Hmm, I see two scenarios here. What if two students have the same emergency contact, say two bothers having their father as emergency contact?
If in such a case you store only one record (the father) in the emergency contact table, you don't want to delete the emergency contact if only one of them leaves. You'd delete the emergency contact for the other one. So you'd need additional logic, when to delete an emergency contact. You could put that in a trigger.
You use a less sophisticated approach and multiple rows from the emergency contact table can map to one person in real life. In that case you can pull the reference to the student directly into the emergency contact table and use ON DELETE CASCADE there.
CREATE TABLE student
(studentid int(5),
name string(16),
...
PRIMARY KEY (studentid),
...);
...
CREATE TABLE emergencycontact
(emergencycontactid int(5),
studentid int(5),
name string(16),
...
PRIMARY KEY (emergencycontactid),
FOREIGN KEY (studentid)
REFERENCES student
(studentid),
...);
The second might be tempting but the "clean way" is the first one, as the second allows contradicting data. From what you posted you're already on the "clean way". But a mentioned that required triggers.

Related

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

SQL JDBC: parent key not found BUT the parent values have already been inserted into the database

I'm having a problem inserting mock data into the database
The table I'm trying to insert the values into is called 'purchased'
which has some foreign key values such as patient_id, pharmacy_id, drug_id.
The other 3 tables 'patient', 'pharmacy', 'drug' have already been added to the database successfully.
Patient table has a foreign key which is doctor_id. The 'doctor' table has also been added to the database.
Since I know that a foreign key patient_id in 'purchased' table depends upon another foreign key doctod_id, so I've done something like this
foreign key (patient_id, receipt_no) references patient(patient_id,doctor_id) ON DELETE CASCADE
Not sure what I've missed here, why does sqlplus complain that the parent key is not found? Just working for a database class project so I'm still learning.
Here's .sql code file
https://gist.github.com/mopkaloppt/de8fbf64c4d5711c90e2b389a72911ba
Any kind of help will be much appreciated.. I'm freaking out a bit now as I've been struggling with this for a while and it's due soon :(
This part of your FK constraint looks confused:
foreign key (patient_id, receipt_no) references patient(patient_id,doctor_id) ON DELETE CASCADE
^^^^^^^^^^ ^^^^^^^^^
You are getting the errors because there is no receipt_no in your purchased data that matches a doctor_id. This is perhaps unsurprising as the data are unrelated.
Having looked at the data in your patient table, it seems you haven't got the database design quite right. There's duplication in that table: if a patient has multiple illnesses or sees multiple doctors then there are repeated values for all other columns. This is also getting in the way of your FK constraint: you are trying to link a row in purchased to a patient, but there are multiple rows for some patients, so which row do you link to?
It seems you have a many-to-many relationship between patients and illnesses (a patient can have multiple illnesses, multiple patients can have an illness), and also between patients and doctors (a patient can be seen by multiple doctors, a doctor can see multiple patients). So, I would recommend introducing new tables for the relationships between patients and illnesses, and between patients and doctors. For example, here's the table you could use for the relationship between patients and doctors. Insert one row into this table for each combination of patient and doctor:
create table patient_doctor (
patient_id char(4) not null,
doctor_id char (4) not null,
primary key (patient_id, doctor_id),
foreign key (patient_id) references patient(patient_id) on delete cascade,
foreign key (doctor_id) references doctor(doctor_id) on delete cascade);
A linking table such as this is the standard way of representing a many-to-many connection in a relational database.
You don't yet have a table for illnesses, so I'll leave you to create a table for them, a linking table similar to that for patients and doctors (patient_illness perhaps), and the data in both tables.
Once you've done that, remove the doctor_id and illness columns from patient, remove the duplicate rows and make the patient PK depend only on patient. Your FK constraint from purchased to patient can then reference only patient_id.
Hopefully after doing all of this you should see your FK constraint violation errors go away.

Is it possible to create a foreign key constraint using "NOT IN" logic

Is it possible to add a foreign key constraint on a table which will allow values which do NOT exist in another table?
In the example below, both tables contain the field USER_ID. The constraint is that a customer and and an employee cannot have the same USER_ID value.
I am very limited in adding new tables or altering one of the tables in any way.
CUSTOMER
--------------------------
USER_ID varchar2(10)
EMPLOYEE
--------------------------
USER_ID varchar2(10)
I thought of a few workarounds, such as a view which contains the data from both tables or adding an insert trigger on the table I can modify.
No, no such thing exists, though it is possible to fake.
If you want to do this relationally (which would be a lot better than views/triggers) the easy option is to add a E to all employee IDs and a C to all customer IDs. However, this won't work if you have other attributes and you want to ensure they're not the same person (i.e. you're not just interested in the ID).
If this is the case you need to create a third table, let's call it PEOPLE:
create table people (
user_id varchar2(10) not null
, user_type varchar2(1) not null
, constraint pk_people primary key (user_id)
, constraint chk_people_user_types check ( user_type in ('C','E') )
);
C would stand for customer and E for employee in the check constraint. You then need to create a unique index/constraint on PEOPLE:
create index ui_people_id_type on people ( user_id, user_type );
Personally, I'd stop here and completely drop your CUSTOMER and EMPLOYEE tables; they no longer have any use and your problem has been solved.
If you don't have the ability to add new columns/tables you need to speak to the people who do and convince them to change it. Over-complicating things only leads to errors in logic and confusion (believe me - using a view means you need a lot of triggers to maintain your tables and you'll have to ensure that someone only ever updates the view). It's a lot easier to do things properly, even if they take longer.
However, if you really want to continue you alter your CUSTOMER and EMPLOYEE tables to include their USER_TYPE and ensure that it's always the same for every row in the table, i.e.:
alter table customers add user_type default 'C' not null;
alter table customers add constraint chk_customers_type
check ( user_type is not null and user_type = 'C' );
Unless you are willing to change the data model as someone else has suggested, the simplest way to proceed with the existing structure while maintaining mutual exclusion is to issue check constraints on the user_ids of both tables such that they validate only to mutually exclusive series.
For example, you could issue checks to ensure that only even numbers are assigned to customers and odd numbers to employees ( or vice-versa).
Or, since both IDS are varchar, stipulate using your check constraint that the ID begins with a known substring, such as 'EMP' or 'CUST'.
But these are only tricks and are hardly relational. Ideally, one would revise the data model. Hope this helps.

How to construct a Junction Table for Many-to-Many relationship without breaking Normal Form

I have these two tables, Company and Owner.
Right now they are both in Normal Form, but I need to create a Many-to-Many relationship between them, since one Company can have many Owners and one Owner can have many Companies.
I have previously gotten an answer to whether adding an array of CompanyOwners (with Owner UUIDs) to Companies would break Normal Form, It will break Normal Form, and have been able to gather that what could be used is a Junction Table, see thread.
My question is as following: will the creation of an additional Junction Table as shown below, break Normal Form?
-- This is the junction table.
CREATE TABLE CompanyOwners(
Connection-ID UUID NOT NULL, // Just the ID (PK) of the relationship.
Company-ID UUID NOT NULL REFERENCES Company (Company-ID),
Owner-ID UUID NOT NULL REFERENCES Owner (Owner-ID),
CONSTRAINT "CompanyOwners" PRIMARY KEY ("Connection-ID")
)
Your structure allows duplicate data. For example, it allows data like this. (UUIDs abbreviated to prevent horizontal scrolling.)
Connection_id Company_id Owner_id
--
b56f5dc4...af5762ad2f86 4d34cd58...a4a529eefd65 3737dd70...a359346a13b3
0778038c...ad9525bd6099 4d34cd58...a4a529eefd65 3737dd70...a359346a13b3
8632c51e...1876f6d2ebd7 4d34cd58...a4a529eefd65 3737dd70...a359346a13b3
Each row in a relation should have a distinct meaning. This table allows millions of rows that mean the same thing.
Something along these lines is better. It's in 5NF.
CREATE TABLE CompanyOwners(
Company_ID UUID NOT NULL references Company (Company_ID),
Owner_ID UUID NOT NULL references Owner (Owner_ID),
PRIMARY KEY (Company_ID, Owner_ID)
);
Standard SQL doesn't allow "-" in identifiers.
This is fine as it is but you could add a couple of more columns like
DateOwned Datetime --<-- when the owner bought the company
DateSold Datetime --<-- when a the owner sold the compnay
After all you would want to know something like is company is still owned by the same owner, and keep track of the company's ownership history etc.

ON UPDATE CASCADE with two columns in a single table in SQL Server [duplicate]

I have a database table called Lesson:
columns: [LessonID, LessonNumber, Description] ...plus some other columns
I have another table called Lesson_ScoreBasedSelection:
columns: [LessonID,NextLessonID_1,NextLessonID_2,NextLessonID_3]
When a lesson is completed, its LessonID is looked up in the Lesson_ScoreBasedSelection table to get the three possible next lessons, each of which are associated with a particular range of scores. If the score was 0-33, the LessonID stored in NextLessonID_1 would be used. If the score was 34-66, the LessonID stored in NextLessonID_2 would be used, and so on.
I want to constrain all the columns in the Lesson_ScoreBasedSelection table with foreign keys referencing the LessonID column in the lesson table, since every value in the Lesson_ScoreBasedSelection table must have an entry in the LessonID column of the Lesson table. I also want cascade updates turned on, so that if a LessonID changes in the Lesson table, all references to it in the Lesson_ScoreBasedSelection table get updated.
This particular cascade update seems like a very straightforward, one-way update, but when I try to apply a foreign key constraint to each field in the Lesson_ScoreBasedSelection table referencing the LessonID field in the Lesson table, I get the error:
Introducing FOREIGN KEY constraint 'c_name' on table 'Lesson_ScoreBasedSelection' may cause cycles or multiple cascade paths.
Can anyone explain why I'm getting this error or how I can achieve the constraints and cascading updating I described?
You can't have more than one cascading RI link to a single table in any given linked table. Microsoft explains this:
You receive this error message because
in SQL Server, a table cannot appear
more than one time in a list of all
the cascading referential actions that
are started by either a DELETE or an
UPDATE statement. For example, the
tree of cascading referential actions
must only have one path to a
particular table on the cascading
referential actions tree.
Given the SQL Server constraint on this, why don't you solve this problem by creating a table with SelectionID (PK), LessonID, Next_LessonID, QualifyingScore as the columns. Use a constraint to ensure LessonID and QualifyingScore are unique.
In the QualifyingScore column, I'd use a tinyint, and make it 0, 1, or 2. That, or you could do a QualifyingMinScore and QualifyingMaxScore column so you could say,
SELECT * FROM NextLesson
WHERE LessonID = #MyLesson
AND QualifyingMinScore <= #MyScore
AND #MyScore <= QualifyingMaxScore
Cheers,
Eric