enforce uniqueness and define foreign key constraint from multiple tables - mysql

this question is applicable on different RDBMS: MySQL and SQL Server
I've been searching for this all night and I can't find it on the net. My problem is all about uniqueness and foreign keys. Consider the following schema:
CREATE TABLE Male
(
StudentID INT,
FirstName VARCHAR(20) NOT NULL,
MiddleName VARCHAR(20) NOT NULL,
LastName VARCHAR(20) NOT NULL,
-- other columns ...
CONSTRAINT male_PK PRIMARY KEY (StudentID),
CONSTRAINT male_UQ UNIQUE (FirstName, MiddleName, LastName)
);
CREATE TABLE Female
(
StudentID INT,
FirstName VARCHAR(20) NOT NULL,
MiddleName VARCHAR(20) NOT NULL,
LastName VARCHAR(20) NOT NULL,
-- other columns ...
CONSTRAINT Female_PK PRIMARY KEY (StudentID),
CONSTRAINT Female_UQ UNIQUE (FirstName, MiddleName, LastName)
);
CREATE TABLE ClassList
(
ClassID INT,
Name VARCHAR(30),
ClassYear INT,
-- other columns ...
CONSTRAINT ClassList_PK PRIMARY KEY (ClassID),
CONSTRAINT ClassList_UQ UNIQUE (Name, ClassYear)
);
The reason why table Male and Female are separated is because they are maintained by different sql accounts.
The problem now is the Association Class.
CREATE Student_Class
(
SudentID INT,
ClassID INT,
CONSTRAINT tb_UQ UNIQUE (StudentID, ClassID)
)
So my questions are:
How can I set the uniqueness of the StudentID from both tables Male and Female. I can't find that on the net.
How can I enforce Foreign Key constraints in which the value column of the association class table come from two tables: Male and Female?
Suggestions are also Accepted.
Thank You

This is why attribute splitting is bad - it starts "infecting" the other tables in odd ways.
You can have:
CREATE Student_Class
(
MaleStudentID INT NULL,
FemaleStudentID INT NULL,
ClassID INT NOT NULL,
CONSTRAINT tb_UQ UNIQUE (MaleStudentID, FemaleStudentID, ClassID)
)
Since MySQL, so far as I'm aware, doesn't support CHECK constraints, you'll also have to create a trigger that ensures only one, and exactly one, of MaleStudentID and FemaleStudentID is NOT NULL.
You can now apply your foreign key constraints in an obvious manner.
For SQL Server, you would have a check constraint:
ALTER TABLE Student_Class ADD CONSTRAINT CK_Students_Nullability CHECK (
(MaleStudentID is null and FemaleStudentID is not null) or
(MaleStudentID is not null and FemaleStudentID is null)
)

I don't think it's a great idea to let user privileges dictate your schema but if you have to then I think the idea mentioned by HerpaMoTeH is a good one. A third table with id, gender and with the id used as an FK in the gender specific tables and student_class.
Another solution is to just split your student_class table into two:
male_student_class and female_student_class
Alternatively you could create 2 sets of stored procedures (male and female) for CRUD operations on a single student table. Then you would deny priviliges to the actual table for your users but grant them access to the appropriate set of stored procedures. Your schema would be simplified to:
CREATE TABLE Student
(
StudentID INT,
GenderId INT,
FirstName VARCHAR(20) NOT NULL,
MiddleName VARCHAR(20) NOT NULL,
LastName VARCHAR(20) NOT NULL,
-- other columns ...
);
CREATE Student_Class
(
SudentID INT,
ClassID INT,
CONSTRAINT tb_UQ UNIQUE (StudentID, ClassID)
)

You can:
Set a range for StudentID to identify Male / Female. For example 10000 to 19999 for male and 20000 to 29999 for female
Alternately, you can add a redundant column "Sex" in both tables, with M for male and F for female and make that part of the reference
Raj

Related

Which database scheme is better for performance aspect?

----Scheme1----
CREATE TABLE college (
id INT AUTO_INCREMENT,
name VARCHAR(250) NOT NULL,
address VARCHAR(250),
PRIMARY KEY (id)
);
CREATE TABLE student (
college INT NOT NULL,
username VARCHAR(50) NOT NULL,
name VARCHAR(100),
FOREIGN KEY (college) REFERENCES college(id),
CONSTRAINT pk PRIMARY KEY (college,username)
);
CREATE TABLE subject (
college INT NOT NULL,
id INT NOT NULL,
name VARCHAR(100),
FOREIGN KEY (college) REFERENCES college(id),
CONSTRAINT pk PRIMARY KEY (college,id)
);
CREATE TABLE marks (
college INT NOT NULL,
student VARCHAR(50) NOT NULL,
subject INT NOT NULL,
marks INT NOT NULL,
// forget about standard for this example
FOREIGN KEY (college) REFERENCES college(id),
FOREIGN KEY (student) REFERENCES student(username),
FOREIGN KEY (subject) REFERENCES subject(id),
CONSTRAINT pk PRIMARY KEY (college,subject,student)
);
----Scheme2----
CREATE TABLE college (
id INT AUTO_INCREMENT,
name VARCHAR(250) NOT NULL,
address VARCHAR(250),
PRIMARY KEY (id)
);
CREATE TABLE student (
college INT NOT NULL,
id BIGINT NOT NULL AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
name VARCHAR(100),
FOREIGN KEY (college) REFERENCES college(id),
PRIMARY KEY (id)
);
CREATE TABLE subject (
college INT NOT NULL,
id BIGINT NOT NULL AUTO_INCREMENT,
name VARCHAR(100),
FOREIGN KEY (college) REFERENCES college(id),
PRIMARY KEY (id)
);
CREATE TABLE marks (
student VARCHAR(50) NOT NULL,
subject INT NOT NULL,
id BIGINT NOT NULL AUTO_INCREMENT,
marks INT NOT NULL,
// forget about standard for this example
FOREIGN KEY (student) REFERENCES student(id),
FOREIGN KEY (subject) REFERENCES subject(id),
PRIMARY KEY (id)
);
Looking at the above database schemes it looks like Scheme1 will give better performance while searching for the result of a specific student and faster in filtering results but it feels like it is not in all normalized forms. While Scheme2, on the other hand, looks to be fully normal but might require more JOIN operations to fetch certain results or filter the data.
Please tell me if I'm wrong about my Schemes here, also tell me which one is better?
I would go for Schema 2: when it comes to reference a table, it is easier done by using a single column (auto_incremented primary key in Schema 1) than a combination of columns (coumpound primary keys in Schema 1). Also, as commented by O.Jones, Schema 2 assumes that two students in the same college cannot have the same name, which does not seem sensible.
There are other issues with Schema 1, eg the foreign key that relates the marks to students is malformed (you would need a coumpound foreign keys that include the college id instead of just the student name).
With properly defined foreign keys referencing primary keys, performance will not be a problem; joins perform good in this situation.
But one flaw should be fixed in Schema 2, that is to store a reference to the college in the marks table. You don't need this, since a student belongs to a college (there is a reference to the college in the student table).
Also, I am unsure that a subject should belong to a college: isn't it possible that the same subject would be taught in different colleges?
Finally, I would suggest giving clearer names to the foreign key columns, like student_id instead of student, and college_id instead of college.
It's difficult to assess whether a schema is normalized without first knowing the the relationships between entities. Can a student be associated with only one college? Can a student be associated multiple times over with the same subject, getting different marks?
Declaring foreign keys maintains referential integrity but slows down insertions and updates. You can get the same functionality without declaring the fks, but you may end up with some orphaned records. The fact that a particular index is used for a fk, or not, makes no difference to SELECT query performance.
JOIN operations use indexes. So do fks. So if you have the correct indexes, your JOIN operations will be efficient. But it's impossible to know which indexes are the best without knowing your JOIN queries.
Conventionally, each table's id column comes first. And many designers name each id column after the table in which it appears, for example college.college_id rather than college.id. That makes JOIN queries slightly easier to read.
You should use a surrogate primary key in the student table (student.student_id) rather than using the student's name as part of the primary key. JOINing on id values is faster than joining on VARCHAR() values. And, some students may share names. (In the real world, peoples's dates of birth accompany their names in tables: it helps tell people apart.)
I think your marks table should contain these columns:
CREATE TABLE marks (
student_id INT NOT NULL,
subject_id INT NOT NULL,
marks INT NOT NULL,
// foreign keys as needed
PRIMARY KEY (student_id, subject_id)
);
Can a student have multiple marks for the same subject? In that case use a marks_id as the pk instead of (student_id, subject_id).

MySQL create table one to many, what is the best option below?

I have below data and using mysql. Person_name is unique and TelephoneNumbers are unique per person.
Person_name1=TelephoneNumber1, TelephoneNumber2, TelephoneNumber3...
Person_name2=TelephoneNumber4, TelephoneNumber5, TelephoneNumber6...
Option 1. Create 1:Many master and child table.
CREATE TABLE Person (
personName varchar(50) NOT NULL,
id int NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id),
UNIQUE KEY personName (personName)
);
CREATE TABLE Telephone (
telephoneNumber int,
mappingId int,
PRIMARY KEY (telephoneNumber),
foreign key(mappingId) references Person(id)
);
Option 2. Create one table with personName, telephoneNumber as Composite Key.
CREATE TABLE
Person_Telephone (
personName varchar(50) NOT NULL,
telephoneNumber int NOT NULL,
PRIMARY KEY(personName, telephoneNumber)
);
Option 1 is it over complicating creating two tables for just two fields?
Option 2 looks perfect and will there be any issues if Option 2 chosen over Option 1?
The option 2 gives you duplicate persons that you must control in every query.
The best is have the entities separate, it's a classic 1-N relation
Since users can have multiple phone numbers, I think 2 tables would be the best solution.
CREATE TABLE person (
PRIMARY KEY (id) AUTO_INCREMENT,
person_name VARCHAR(45) NOT NULL,
);
CREATE TABLE phone_number (
PRIMARY KEY (id) AUTO_INCREMENT,
phone_number VARCHAR(11) NOT NULL,
FOREIGN KEY (person_id) REFERENCES person(id)
)
Now you can simply JOIN the tables like this:
SELECT
t1.id,
t1.person_name,
t2.phone_number
FROM person t1
LEFT JOIN phone_number t2
ON (t1.id = t2.person_id);

MySQL one to many with primary choice

Say I have a bunch of people with multiple phone numbers. In a MySQL database I'd have a Person table and a Phone Number table with a many to one relationship.
Now I want to make one of those numbers the primary phone number and only allow one primary number per person. How would I model this?
Try the schema below. It will prevent entries that try to assign more than one primary number per person.
CREATE TABLE person (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`first_name` VARCHAR(50) NOT NULL,
`last_name` VARCHAR(50) NOT NULL,
PRIMARY KEY(`id`)
);
CREATE TABLE phonenumber (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`phonenumber` VARCHAR(10) NOT NULL,
`person_id` INT(11) UNSIGNED NOT NULL,
`is_primary` ENUM('1'),
PRIMARY KEY(`id`),
UNIQUE KEY idx_person_primary (`person_id`, `is_primary`),
UNIQUE KEY idx_person_phone (`phonenumber`, `person_id`)
);
INSERT INTO person (first_name, last_name) VALUES ('Michael', 'Jones');
INSERT INTO phonenumber (phonenumber, person_id, is_primary) VALUES ('9876543210', 1, 1);
INSERT INTO phonenumber (phonenumber, person_id, is_primary) VALUES ('1234567890', 1, NULL);
INSERT INTO phonenumber (phonenumber, person_id, is_primary) VALUES ('1234567891', 1, NULL);
This will allow the DB to police a single primary phone number for each person. For example if you try to assign another primary phone number to Michael Jones:
INSERT INTO phonenumber (phonenumber, person_id, is_primary) VALUES ('0123211234', 1, 1);
You will get a "Duplicate entry '1-1' for key 'idx_person_primary'" error.
http://sqlfiddle.com/#!9/dbb3c7/1
The "exactly one primary phone number" is tricky. One way uses triggers. Other databases offer expression-based indexes. This is tricky because:
The constraint spans two tables.
Guaranteeing exact "one-ness" across updates is tricky.
But one method in MySQL that comes close and doesn't use triggers:
create table persons (
personId int auto_increment primary key,
primary_personPhonesId int,
. . .
);
create table personPhones (
personPhonesId int auto_increment primary key,
personId int,
. . .
foreign key (personId) references persons (personId),
unique (personId, personPhonesId) -- seems redundant but needed
);
alter table persons
add foreign key (personId, primary_personPhonesId) on personPhones(personId, personPhonesId);
It is tempting to declare primary_personPhonesId as not null. However, that makes it difficult to insert rows into the two tables.
An alternative method uses computed columns:
create table persons (
personId int auto_increment primary key,
. . .
);
create table personPhones (
personPhonesId int auto_increment primary key,
personId int,
isPrimary boolean,
. . .
foreign key (personId) references persons (personId),
primaryId as (case when isPrimary then personPhonesId end),
unique(primaryId)
);
Similar to the previous solution, this does not guarantee that isPrimary is always set.
You can try the below mentioned design:
Person (Id (PK),name,....)
TelephoneNumber (Id(PK), telNo, PersonId(FK))
PrimaryTelNo (PersonId(FK), TelId(FK))
You can create a table showing mapping of TelId and PersonId and declare the combination of TelId and PersonId as composite primary key
The simplest way is to make the 'first' one primary, but this becomes tricky when you want to change which one is primary. In that case, I believe you can do this...
CREATE TABLE my_table
(person_id INT NOT NULL
,phone VARCHAR(12) not null
,is_primary enum('1') null
,primary key(person_id,phone)
, unique (person_id,is_primary)
);
INSERT INTO my_table VALUES
(1,'123',1),
(1,'234',null),
(1,'345',null),
(2,'456',null),
(2,'567',1),
(2,'678',null);
So, the enum allows values of 1 and null, but while there can be several nulls, there can only be one '1' per person. However, this solution doesn't preclude the possibility that none of the numbers are primary!
You should create a third table person_primary_number with only two fields:
person_id
phone_number_id
In this table you should insert the ids of the person and his primary number. The primary key of this table is on these two columns.
Another way is to add primary_number_id directly to the person table. This is probably the simplest solution.
Then you should have:
person
—————-
id (primary key int autoincrement)
primary_number_id (foreign key for phone_number.id)
name
...
phone_number
———————————-
id (primary key int autoincrement)
person_id (foreign key for person.id)
phone_number
The only problem with this solution is that you can assign as primary phone the number of somebody else.
This violates a strong principle of schema design -- don't pack a list into a cell. But...
If you only need to display the phone number to some human who will be doing a call, and
If that human possibly needs to see non-primary numbers, then
Consider having a VARCHAR(100) column that has a commalist that starts with the 'primary' phone number and continues with alternative numbers.
Note that the application would be responsible for putting the list together, and dealing with updates. Or you could push this back onto the user by providing a UI that asks for "phone number(s), starting with the preferred one to call you with; please separate with commas."

Intersection of Two Tables in SQL

Basically I need to create a new table that uses specific information from two other tables.
For example, I have a table called person with the elements person_id, first_name, last_name, gender, age, and fav_quote. I have a second table called department with the elements dept_id, dept_name, and building. I now need to create and intersection table with the person_id and dept_id elements included. And both must be the primary key (which I assume just means PRIMARY KEY (person_id, dept_id) command in my source).
CREATE TABLE person (
person_id INT(8) NOT NULL auto_increment,
first_name VARCHAR(25) NOT NULL,
last_name VARCHAR(25) NOT NULL,
gender VARCHAR(1),
age INT(8),
fav_quote TEXT,
PRIMARY KEY (person_id)
);
CREATE TABLE department (
dept_id INT(8) NOT NULL auto_increment,
dept_name VARCHAR(25) NOT NULL,
building VARCHAR(25) NOT NULL,
PRIMARY KEY (dept_id)
);
That is the code I have for the initial two tables I'm just not sure how to create an intersection and, having gone back over my notes, I can't find the instructions on how to write it.
You got the primary key part right. I'd add foreign keys to your existing table in order to prevent creating interactions with people or departments that don't exist:
CREATE TABLE person_department
person_id INT(8) NOT NULL,
dept_id INT(8) NOT NULL,
PRIMARY KEY(person_id, dept_id),
FOREIGN KEY(person_id) REFERENCES person(person_id),
FOREIGN KEY(dept_id) REFERENCES department(dept_id)
)
You need a table with 2 fields; person_id and dept_id. The table will have foreign keys to the two tables person and department primary keys’ and a composite primary key of both.
Also, this table is only necessary if there is a one to many relationship of person to department. Otherwise just add dept_id as a foreign key in person.

One field with two references in MySQL

I have three tables:
CREATE TABLE Address (
ResidentID CHAR(5) NOT NULL,
Location varchar(255) NOT NULL,
KEY ResidentID(ResidentID)
);
CREATE TABLE Customer (
CustomerID CHAR(5) NOT NULL,
ContactName varchar(40) NOT NULL,
PRIMARY KEY (CustomerID)
);
CREATE TABLE Supplier (
SupplierID CHAR(5) NOT NULL,
SupplierName varchar(40) NOT NULL,
PRIMARY KEY (SupplierID)
);
I want to store CustomerID and SupplierID in the Address.ResidentID field with using of foreign keys:
ALTER TABLE Address ADD CONSTRAINT fk_CustomerID1 FOREIGN KEY(ResidentID) REFERENCES Customer(CustomerID);
ALTER TABLE Address ADD CONSTRAINT fk_SupplierID1 FOREIGN KEY(ResidentID) REFERENCES Supplier(SupplierID);
But second 'ALTER TABLE' raises Error: relation already exists
Any suggestions?
Data example:
CustomerID ContactName
C0001 Den
SupplierID ContactName
S0001 John
So Address table should contains:
ResidentID Location
C0001 Alaska
S0001 Nevada
You need to either reference addresses from the Customer / Supplier (if they only have one) or two different columns.
The reason you see in this SQLFiddle You cannot INSERT the required columns into the Address table if the ResidentID references BOTH tables. You could only insert lines that would match the contents of Customer AND Supplier but you want an OR connection that you can't create that way.
(Note: In my solutions I assume addresses to be optional. As Tom pointed out in the comments that may not be what you wanted, or expected. Make sure to mark the FK Columns in the first solution as NOT NULL if you want addresses to be mandatory, its more complicated for the second one. You have to mind the correct insertion order then.)
Either:
CREATE TABLE Address (
AddressID CHAR(5) NOT NULL,
Location varchar(255) NOT NULL,
PRIMARY KEY (AddressID)
);
CREATE TABLE Customer (
CustomerID CHAR(5) NOT NULL,
AddressID CHAR(5),
ContactName varchar(40) NOT NULL,
PRIMARY KEY (CustomerID)
);
CREATE TABLE Supplier (
SupplierID CHAR(5) NOT NULL,
AddressID CHAR(5),
SupplierName varchar(40) NOT NULL,
PRIMARY KEY (SupplierID)
);
ALTER TABLE Customer ADD CONSTRAINT fk_AddressID_Cust FOREIGN KEY(AddressID) REFERENCES Address(AddressID);
ALTER TABLE Supplier ADD CONSTRAINT fk_AddressID_Supp FOREIGN KEY(AddressID) REFERENCES Address(AddressID);
or
CREATE TABLE Address (
CustomerID CHAR(5),
SupplierID CHAR(5),
Location varchar(255) NOT NULL,
PRIMARY KEY (CustomerID, SupplierID)
);
CREATE TABLE Customer (
CustomerID CHAR(5) NOT NULL,
ContactName varchar(40) NOT NULL,
PRIMARY KEY (CustomerID)
);
CREATE TABLE Supplier (
SupplierID CHAR(5) NOT NULL,
SupplierName varchar(40) NOT NULL,
PRIMARY KEY (SupplierID)
);
ALTER TABLE Address ADD CONSTRAINT fk_CustomerID1 FOREIGN KEY(CustomerID) REFERENCES Customer(CustomerID);
ALTER TABLE Address ADD CONSTRAINT fk_SupplierID1 FOREIGN KEY(SupplierId) REFERENCES Supplier(SupplierID);
The approach you're trying is (a) not possible and (b) undesirable even if it was possible.
The best approach is to have a CustomerAddress table and a SupplierAddress table, each with a single FK to the matching base table; or if you must, a cross-reference table with appropriate constraints.
If your motivation for having a single Address table was code reuse, you can still do that ... think in terms of a template xxxAddress table design that can refer to any base xxx table. You can write non-database code that treats the base table name as a parameter and then could handle any number of xxxAddress tables as you add more base tables over time.
Or if your motivation for having a single Address table was to simplify reporting, you can always create a view or stored proc that returns a union of all such tables + an added field to indicate the base table for each address row.
Angelo I am revising this a bit based on your comment ---
Angelo, I ran your sample code in a local MySQL instance (not SQLFiddle) and observed an error.
I was surprised (you learn something every day) that MySQL did allow two foreign key constraints to be defined on the same field; however when you attempt to insert data, when trying to point the FK to the Customer table, I get an error saying a foreign key constraint fails referencing the Supplier table; and vice versa for the insert trying to point the FK to the Supplier table.
So my revised statement is (a) it is possible to create the hydra-headed FK in at least some DBMSs -- verified in MySQL, MS SQL Server and Oracle -- although (b) this only makes sense to use when the foreign key can refer to the same logical entity by ID across multiple tables (e.g. to ensure there is a corresponding record in all required tables, for example); and (c) if used to refer to multiple tables where the primary key is NOT the same logical entity, only works if by chance the same primary key value just happens to exist in all referenced tables, which is likely to lead to subtle, hard-to-find errors.
In other words, your example would work when attempting to insert a record referring to Customer ID=3 only if there was also a Supplier ID=3, which are really logically unrelated.
So my slightly revised answer to the OP is, what you're trying to do is not possible (or logical) when the foreign key is referring to different ENTITIES, as in the OP example of Customers and Suppliers.