This was originally going to be an "update" on the logical schema presented in another question here: Getting ERROR 1701, ERROR 1452 and ERROR 1305 errors in MySQL - Need some expertise...
I think I have successfully verified this schema to 1st and 2nd Normal Form, but am unsure if this meets 3rd Normal Form. Here is the model in question:
And here is the associated code (note: for some reason I cannot recreate 1:1 relationships in the sql code as illustrated in the logical model above):
-- database_schema.sql.
-- This sql script creates the structure.
-- of the rugby club database.
DROP DATABASE IF EXISTS database_rugby;
CREATE DATABASE database_rugby;
USE database_rugby;
-- Create the "person" table.
--
-- This table has one:one relationships
-- with the parent, coach and player
-- tables.
DROP TABLE IF EXISTS `person` ;
CREATE TABLE `person` (
`personID` INT(5) NOT NULL AUTO_INCREMENT ,
`firstName` VARCHAR(50) NOT NULL ,
`lastName` VARCHAR(50) NOT NULL ,
`dateOfBirth` DATE NOT NULL ,
`streetAddress` VARCHAR(150) NOT NULL ,
`suburbAddress` VARCHAR(150) NULL DEFAULT NULL ,
`cityAddress` VARCHAR(150) NOT NULL ,
`photo` BLOB NULL DEFAULT NULL ,
PRIMARY KEY (`personID`))
ENGINE = InnoDB;
-- Create the "parent" table.
DROP TABLE IF EXISTS `parent` ;
CREATE TABLE `parent` (
`parentID` INT(5) NOT NULL ,
`personID` INT(5) NOT NULL ,
PRIMARY KEY (`parentID`, `personID`),
FOREIGN KEY (`personID`) REFERENCES `person` (`personID`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB;
-- Create the "school" table.
DROP TABLE IF EXISTS `school` ;
CREATE TABLE `school` (
`schoolID` INT(5) NOT NULL AUTO_INCREMENT ,
`schoolName` VARCHAR(100) NOT NULL ,
PRIMARY KEY (`schoolID`))
ENGINE = InnoDB;
-- Create the "player" table.
--
-- Inherits fields from the "person"
-- and "school" tables.
DROP TABLE IF EXISTS `player` ;
CREATE TABLE `player` (
`playerID` INT(5) NOT NULL ,
`personID` INT(5) NOT NULL ,
`schoolID` INT(5) NOT NULL ,
PRIMARY KEY (`playerID`, `personID`),
FOREIGN KEY (`personID`)
REFERENCES `person` (`personID`)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (`schoolID`)
REFERENCES `school` (`schoolID`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB;
-- Create the "coach" table.
DROP TABLE IF EXISTS `coach`;
CREATE TABLE `coach`(
`coachID` INT(5) NOT NULL ,
`dateBeganCoaching` DATE NOT NULL ,
`personID` INT(5) NOT NULL ,
PRIMARY KEY (`coachID`, `personID`),
FOREIGN KEY (`personID`)
REFERENCES `person` (`personID`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB;
-- Create the "family" table.
--
-- This is a linking table
-- that describes the many:many
-- relationship between "parent"
-- and "player" tables.
DROP TABLE IF EXISTS `family` ;
CREATE TABLE `family` (
`parentID` INT(5) NOT NULL ,
`playerID` INT(5) NOT NULL ,
FOREIGN KEY (`playerID` )
REFERENCES `player` (`playerID`)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (`parentID`)
REFERENCES `parent` (`parentID`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
-- Create the "grade" table.
DROP TABLE IF EXISTS `grade`;
CREATE TABLE `grade`(
`gradeID` INT(5) NOT NULL AUTO_INCREMENT ,
`gradeName` VARCHAR(50) NOT NULL ,
`minWeight` INT(3) NOT NULL ,
`maxWeight` INT(3) NOT NULL ,
`minAge` INT(3) NOT NULL ,
`maxAge` INT(3) NOT NULL ,
`ballSize` INT(1) NOT NULL ,
PRIMARY KEY (`gradeID`) )
ENGINE = InnoDB;
-- Create the "coachQualification" table.
DROP TABLE IF EXISTS `coachQualification` ;
CREATE TABLE `coachQualification` (
`qualID` INT(5) NOT NULL AUTO_INCREMENT ,
`qualName` CHAR(5) NOT NULL ,
`gradeID` INT(5) NOT NULL ,
PRIMARY KEY (`qualID`) ,
FOREIGN KEY (`gradeID`)
REFERENCES `grade` (`gradeID`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB;
-- Create the "homePhone" table.
DROP TABLE IF EXISTS `homePhone` ;
CREATE TABLE `homePhone` (
`homePhoneID` INT(5) NOT NULL AUTO_INCREMENT ,
`homeNumber` CHAR(9) NOT NULL ,
PRIMARY KEY (`homePhoneID`))
ENGINE = InnoDB;
-- Create the "mobilePhone" table.
DROP TABLE IF EXISTS `mobilePhone` ;
CREATE TABLE `mobilePhone` (
`mobilePhoneID` INT(5) NOT NULL AUTO_INCREMENT ,
`mobileNumber` CHAR(10) NULL DEFAULT NULL ,
PRIMARY KEY (`mobilePhoneID`))
ENGINE = InnoDB;
-- Create the "emailAddress" table.
DROP TABLE IF EXISTS `emailAddress` ;
CREATE TABLE `emailAddress` (
`emailAddressID` INT(5) NOT NULL AUTO_INCREMENT ,
`emailAddress` CHAR(10) NULL DEFAULT NULL ,
PRIMARY KEY (`emailAddressID`))
ENGINE = InnoDB;
-- Create the "Contact" table
--
-- This is a linking table
-- that describes the many:many
-- relationships between "person"
-- and the "homePhone", "mobilePhone",
-- and "emailAddress" tables.
DROP TABLE IF EXISTS `contact` ;
CREATE TABLE `contact` (
`personID` INT(5) NOT NULL ,
`homePhoneID` INT(5) NOT NULL ,
`mobilePhoneID` INT(5) NULL DEFAULT NULL ,
`emailAddressID` INT(5) NULL DEFAULT NULL ,
FOREIGN KEY (`personID` )
REFERENCES `person` (`personID`)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (`homePhoneID`)
REFERENCES `homePhone` (`homePhoneID`)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (`mobilePhoneID`)
REFERENCES `mobilePhone` (`mobilePhoneID`)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (`emailAddressID`)
REFERENCES `emailAddress` (`emailAddressID`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB;
-- Create the "qualificationSet" table.
--
-- This is a linking table
-- that describes the many:many
-- relationship between "coach"
-- and "coachQualification" tables.
DROP TABLE IF EXISTS `qualificationSet` ;
CREATE TABLE `qualificationSet` (
`coachID` INT(5) NOT NULL ,
`qualID` INT(5) NOT NULL ,
FOREIGN KEY (`coachID`)
REFERENCES `coach` (`coachID`)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (`qualID`)
REFERENCES `coachQualification` (`qualID`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB;
-- Create the "team" table.
DROP TABLE IF EXISTS `team` ;
CREATE TABLE `team` (
`teamID` INT(5) NOT NULL AUTO_INCREMENT ,
`teamName` VARCHAR(50) NOT NULL ,
`teamYear` INT(2) NOT NULL ,
`gradeID` INT(5) NOT NULL ,
PRIMARY KEY (`teamID`) ,
FOREIGN KEY (`gradeID`)
REFERENCES `grade` (`gradeID`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB;
-- Create the "teamAllocation" table
--
-- this is a linking table for a
-- many:many relationship between
-- team and player tables.
DROP TABLE IF EXISTS `teamAllocation` ;
CREATE TABLE `teamAllocation` (
`teamID` INT(5) NOT NULL ,
`playerID` INT(5) NOT NULL ,
FOREIGN KEY (`teamID` )
REFERENCES `team` (`teamID`)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (`playerID`)
REFERENCES `player` (`playerID`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB;
-- Create the "teamCoachAllocation" table.
--
-- This is a linking table
-- that describes the many:many
-- relationship between "coach"
-- and "team" tables.
DROP TABLE IF EXISTS `teamCoachAllocation` ;
CREATE TABLE `teamCoachAllocation` (
`coachID` INT(5) NOT NULL ,
`teamID` INT(5) NOT NULL ,
FOREIGN KEY (`coachID`)
REFERENCES `coach` (`coachID`)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (`teamID`)
REFERENCES `team` (`teamID`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB;
From these links below:
This is my understanding of Normalisation to 3NF:
Putting that knowledge from theory into practice is quite hard for me, especially translating that "practise" into working, normalised MySQL code. If someone is able to help me go through the model and give me some pointers about normalising the model to 3NF, I would appreciate it very much.
Thanks in advance!
I think this isn't in 3NF, around the contact
table. If I'm wrong this is still a bad way of storing the data and should probably be changed.
Sorry if this is a little confused...
It is entirely possible to have the following structure in your contact
table as the entire table is the primary key:
+----------+-------------+---------------+---------+ | personid | homephoneid | mobilephoneid | emailid | +----------+-------------+---------------+---------+ | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 2 | | 1 | 1 | 2 | 3 | +----------+-------------+---------------+---------+
As you can see both homephoneid
and mobilephoneid
are duplicated so updating the table homephone
will result 3 updates to contact
.
I disagree with the data-model as you require a person
to have a homehone
I don't have one, only a mobile. In this situation, when creating a new person
you have to also create a new contact
and a new homephone
.
As contact
is just a primary key and a primary key value cannot be null, you also require the creation of a mobilephone
and an emailaddress
, which means that person
is dependent on emailaddress
.
As emailaddress
is dependent on contact
, which in turn is dependent on person
you've created a circular dependency, which breaks 3NF.
As I see it you have a two options if you want to ensure that people have to have a home phone number:
person
to have one homephone
then add this
into the person
table. It's unique contact level information and
should be stored there.personhomephones
, say, with the primary key personid,
homephoneid
and not put homephoneid
in the contact table.Personally I wouldn't do either of these. I wouldn't ensure that someone has to have a home phone number but instead a primary phone number, where you don't care what type it is. I would allow people to add different methods of contact but allow these not to exist
This would require the following structure:
person
- add primaryPhoneID
primaryphone ( primaryphoneID, phonenumber)
- PK primaryphoneID
Then for the contact methods that are allowed to not exist:
contactType ( contactTypeID, contactType )
- PK contactTypeID
contact ( contactID, contactTypeID, value )
- PK contactID, contactTypeID
personContact ( personID, contactID, contactTypeID )
- PK everythingWhilst this may result in duplication between contact
and primaryphone
they are distinct bits of data and I think this is fine. If you're insistent on not allowing any duplication at all you'd have to separate out the phones from the other methods of contact, which makes the model more complicated:
phonetype ( phoneTypeId, phoneType )
phone ( phoneID, phoneTypeID, phonenumber)
- PK phoneID, phoneTypeID
contactPhone ( personID, phoneTypeID, phoneID )
- PK everything