I'm writing a genealogy application and I'm trying to figure out on aspect of my database model.
I have a table for people:
create table person (
id int unsigned not null primary key
)
And I have a table for names:
create table name (
id int unsigned not null primary key,
person_id int unsigned not null,
first_name varchar(191),
last_name varchar(191),
foreign key (person_id) references person (id)
)
A person can have multiple names because sometimes they have different names in different censuses and other records.
But a person also has one particular name which we should display by default.
My first thought was to add a primary column to the table name:
create table person (
id int unsigned not null primary key
)
create table name (
id int unsigned not null primary key,
person_id int unsigned not null,
first_name varchar(191),
last_name varchar(191),
`primary` tinyint(1) not null default 0,
foreign key (person_id) references person (id)
)
This has a data-integrity issue. Multiple names that belong to a single person could have the primary flag set.
Another way I thought of was to add a primary_name_id column to the person table. This could link back to the primary name in the name table:
create table person (
id int unsigned not null primary key,
primary_name_id int unsigned default null,
foreign key (primary_name_id) references name (id)
)
create table name (
id int unsigned not null primary key,
person_id int unsigned not null,
first_name varchar(191),
last_name varchar(191),
foreign key (person_id) references person (id)
)
This also has has an integrity issue. primary_name_id might point to a name row that belongs to a different person. Also, it requires the primary_name_id column to be nullable because when a person is first created he has no names yet.
The third way I thought about doing this is to include duplicates of the "name" fields in the person record:
create table person (
id int unsigned not null primary key,
first_name varchar(191),
last_name varchar(191),
)
create table additional_name (
id int unsigned not null primary key,
person_id int unsigned not null,
first_name varchar(191),
last_name varchar(191),
foreign key (person_id) references person (id)
)
This doesn't seem like an ideal solution either. Similar kinds of data are being stored in two places. Also changing a person's primary name would require a bit of work. I'd have to insert a new additional_name, update the person, and delete an old additional name.
Is there a better way to do this?
Your second method is basically the right way. But, you want to be sure that the name is for the person. So:
create table person (
id int unsigned not null primary key,
primary_name_id int unsigned default null,
foreign key (id, primary_name_id) references name (person_id, id)
);
create table name (
id int unsigned not null primary key,
person_id int unsigned not null,
first_name varchar(191),
last_name varchar(191),
foreign key (person_id) references person (id),
unique (person_id, id);
);
The unique constraint is a bit of redundancy, but it let's you ensure that the values match across the two tables -- appropriately.
Related
Let's say I have a database of families, persons and dogs. Each family has 0 or more persons, each person has 0 or more dogs and no dogs in the same family may have the same name. Is it possible to declare a uniqueness constraint for dogs that "jumps" one table, i.e. something like
CREATE TABLE Family (
ID int NOT NULL,
LastName varchar(255) NOT NULL,
PRIMARY KEY (ID)
);
CREATE TABLE Person (
ID int NOT NULL,
FamilyID int NOT NULL,
FirstName varchar(255) NOT NULL,
PRIMARY KEY (ID),
FOREIGN KEY (FamilyID) REFERENCES (Family.ID)
);
CREATE TABLE Dog (
ID int NOT NULL,
PersonID int NOT NULL,
Name varchar(255) NOT NULL,
PRIMARY KEY (ID),
FOREIGN KEY (PersonID) REFERENCES (Person.ID),
UNIQUE (Name, Person.FamilyID)
);
? UNIQUE(Name, Person.FamilyID) obviously does not work, but it should make my intentions clear.
If not, (or perhaps even better, actually) is it possible to have something like
CREATE TABLE Family (
ID int NOT NULL,
LastName varchar(255) NOT NULL,
PRIMARY KEY (ID)
);
CREATE TABLE Person (
ID int NOT NULL,
FamilyID int NOT NULL,
FirstName varchar(255) NOT NULL,
PRIMARY KEY (ID),
FOREIGN KEY (FamilyID) REFERENCES (Family.ID)
);
CREATE TABLE Dog (
ID int NOT NULL,
FamilyID int NOT NULL,
PersonID int NOT NULL,
Name varchar(255) NOT NULL,
PRIMARY KEY (ID),
FOREIGN KEY (FamilyID) REFERENCES (Family.ID),
FOREIGN KEY (PersonID) REFERENCES (Person.ID),
UNIQUE (Name, FamilyID)
);
and enforce consistency between Dog.FamilyID and Dog.PersonID? That is, to ensure that the FamilyID is the same for the dog and the person?
Bonus points if a dog can belong to the entire family by , i.e. Dog.PersonID is nullable.
No, you cannot create UNIQUE constraints that span multiple tables (well... maybe in the exotic case of an oracle bitmap join index, but that's a different story).
The typical solution is to use composite keys. That way the column from the first table will be present in the second and third table, and it would be easy to enforce the uniqueness you want.
For example:
CREATE TABLE Family (
ID int NOT NULL,
LastName varchar(255) NOT NULL,
PRIMARY KEY (ID)
);
CREATE TABLE Person (
ID int NOT NULL,
FamilyID int NOT NULL,
FirstName varchar(255) NOT NULL,
PRIMARY KEY (ID),
UNIQUE (ID, FamilyID), -- unique composite constraint
FOREIGN KEY (FamilyID) REFERENCES Family (ID)
);
CREATE TABLE Dog (
ID int NOT NULL,
PersonID int NOT NULL,
FamilyID int NOT NULL,
Name varchar(255) NOT NULL,
PRIMARY KEY (ID),
FOREIGN KEY (PersonID, FamilyID) REFERENCES Person (ID, FamilyID),
UNIQUE (Name, FamilyID)
);
Forget it. Just attach the dog to a person. Then look up the family from the person.
Trying to managing redundant relationships across tables is just fraught with problems. It is quite easy to get the family looking up the person.
In other words, remove familyId from the dogs table.
I have the following tables:
CREATE TABLE employee(
id INT(11) UNSIGNED PRIMARY KEY,
namee VARCHAR(60) NOT NULL)
ENGINE = InnoDB;
CREATE TABLE consierge(
id INT(11) UNSIGNED PRIMARY KEY,
namee VARCHAR(60) NOT NULL,
zonee VARCHAR(20),
functionn VARCHAR(20),
FOREIGN KEY (id) REFERENCES employee(id))
ENGINE = InnoDB;
CREATE TABLE guard(
id INT(11) UNSIGNED PRIMARY KEY,
namee VARCHAR(60) NOT NULL,
rol VARCHAR(20) NOT NULL,
wtime ENUM ('Day','Night'),
FOREIGN KEY (id) REFERENCES employee(id))
ENGINE = InnoDB;
When I try to create an consierge or guard, it firts verify the existence of that id in employee, which is fine. The problem is that, ie, when an id is already in guard (hence in employee) and I create an consierge with that same id, the consierge gets created, so, an employee can be guard and consierge; and I want that an employee can be guard OR consierge EXCLUSIVElY.
You can do this -- without triggers -- using some additional logic with generated columns:
CREATE TABLE employee(
id INT(11) UNSIGNED PRIMARY KEY,
namee VARCHAR(60) NOT NULL,
type varchar(255),
c_id int generated always as (case when type = 'c' then id end) stored,
g_id int generated always as (case when type = 'g' then id end) stored,
check (type in ('c', 'g'))
);
CREATE TABLE consierge (
id INT(11) UNSIGNED PRIMARY KEY,
namee VARCHAR(60) NOT NULL,
zonee VARCHAR(20),
functionn VARCHAR(20),
type varchar(255) generated always as ('c') stored,
unique (type, id),
FOREIGN KEY (id) REFERENCES employee(id)
);
CREATE TABLE guard(
id INT(11) UNSIGNED PRIMARY KEY,
namee VARCHAR(60) NOT NULL,
rol VARCHAR(20) NOT NULL,
wtime ENUM ('Day','Night'),
type varchar(255) generated always as ('g') stored,
unique (stored, id),
FOREIGN KEY (id) REFERENCES employee(id)
);
ALTER TABLE employee ADD CONSTRAINT fk_employee_c_id
FOREIGN KEY (c_id) REFERENCES concierge(id);
ALTER TABLE employee ADD CONSTRAINT fk_employee_g_id
FOREIGN KEY (g_id) REFERENCES guard(id);
This adds computed columns to allow the desired foreign key relationships.
I have one question, so there are two tables:
CREATE TABLE Account (
id INT NOT NULL AUTO_INCREMENT,
user_id INT NOT NULL,
name VARCHAR(35) NOT NULL,
balance DOUBLE NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (user_id) REFERENCES User (id)
);
CREATE TABLE Operation (
id INT NOT NULL AUTO_INCREMENT,
source_id INT NOT NULL,
destination_id INT NULL,
operation_type VARCHAR(20) NOT NULL,
date DATE NOT NULL,
total DOUBLE NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (source_id) REFERENCES Account (id)
-- Wrong!!!
FOREIGN KEY (destination_id) REFERENCES Account (id)
);
Fields source_id and destination_id have to refer to Account table because each operation should have an account source and not required destination account.
If I have set up source_id as a foreign key in the Operation table that refers to the Account table. How can I make the correct relationship between destination_id and Account table?
I believe it should be in a third table, but I can't figure out how it could be organised in a proper way.
this is my code:
CREATE DATABASE exams;
SHOW DATABASES;
CREATE TABLE IF NOT EXISTS students(
student_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
first_name VARCHAR(20) NOT NULL,
middle_name VARCHAR(40),
last_name VARCHAR(40)NOT NULL,
email VARCHAR(60) NOT NULL,
password CHAR(40) NOT NULL,
reg_date DATETIME NOT NULL,
PRIMARY KEY (student_id),
UNIQUE(email));
SHOW table status
INSERT INTO exams_3121(student_id, first_name, middle_name, last_name, email, password, reg_date)
CREATE TABLE entries
(
entrie_id int NOT NULL,
student_id int NOT NULL,
subject_id int,
PRIMARY KEY (entrie_id),
FOREIGN KEY (student_id) REFERENCES student(student_id),
FOREIGN KEY (subject_id)REFERENCES subject(subject_id)
)
CREATE DATABASE subjects;
CREATE TABLE IF NOT EXISTS subjects(
subject_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
subject_name VARCHAR(20) NOT NULL,
level_entery VARCHAR(40)NOT NULL,
exam_board VARCHAR(60) NOT NULL,
PRIMARY KEY (subject_id));
CREATE TABLE entries
(
entrie_id int NOT NULL,
entrie_id int NOT NULL,
subject_id int,
PRIMARY KEY (entrie_id),
FOREIGN KEY (student_id) REFERENCES student(student_id),
FOREIGN KEY (subject_id)REFERENCES subject(subject_id)
)
When I use this code it says cannot add foreign key constraint
and I don't know what to do. Please and thanks in advance.
There are two problems.
First, you got the names of the table you're referencing wrong. The name of the tables are students and subjects, but you wrote student and subject.
Second, the entries table has two entrie_id columns. One of them should be student_id.
CREATE TABLE entries
(
entrie_id int NOT NULL,
student_id int NOT NULL,
subject_id int,
PRIMARY KEY (entrie_id),
FOREIGN KEY (student_id) REFERENCES students(student_id),
FOREIGN KEY (subject_id) REFERENCES subjects(subject_id)
)
Also, if you're creating multiple databases and putting your tables in them, you'll need to refer to tables with their database prefixes if they're different from the one you selected as default with the USE command. As you've written it, you're not actually using the databases you created with CREATE DATABASE.
I've read several posts on this type of error (one of them was quite similar), but I haven't found one yet that resolves the error that I'm having with the foreign key. Since I can recreate the error, I think I have a pretty good idea of what it is. I am new to SQL, however, and I can't resolve the problem. Here's the code from the two affected tables:
create table artist (
artist_id int unsigned not null auto_increment,
first_name nvarchar(50) not null,
last_name nvarchar(50) not null,
about_artist nvarchar(550) not null,
start_of_event datetime,
end_of_event datetime,
item_id int unsigned not null,
user_id int unsigned not null,
last_modified timestamp,
primary key (artist_id),
foreign key (item_id) references
items (item_id),
foreign key (user_id) references
users (user_id)
);
create table items (
item_id int unsigned not null auto_increment,
artist_id int unsigned not null,
item_name nvarchar(50) not null,
item_description nvarchar(150) not null,
last_modified timestamp,
primary key (item_id),
foreign key (artist_id) references
artist(artist_id)
);
My item_id is item_id int unsigned not null, in the first table, but item_id int unsigned not null auto_increment, in the second table. The second table is the one where it is the primary key. I can't do two auto increments in one table, and if I reverse the order of the tables, the same thing happens with a different column. How can I resolve this? Thank you.
Thinking about my comment, I'm increasingly confident that the link table is the way you need to go.
create table artist (
artist_id int unsigned not null auto_increment,
first_name nvarchar(50) not null,
last_name nvarchar(50) not null,
about_artist nvarchar(550) not null,
start_of_event datetime,
end_of_event datetime,
user_id int unsigned not null,
last_modified timestamp,
primary key (artist_id),
foreign key (user_id) references
users (user_id)
);
create table items (
item_id int unsigned not null auto_increment,
artist_id int unsigned not null,
item_name nvarchar(50) not null,
item_description nvarchar(150) not null,
last_modified timestamp,
primary key (item_id)
);
create table artist_item (
artist_id int unsigned not null,
item_id int unsigned not null
foreign key (artist_id) references
artist(artist_id),
foreign key (item_id) references
items(item_id)
);
You could add an auto-increment field to artist_item, or have the two ID fields as a composite key.
With this, you can query for all items relating to an artist (e.g. one artist has several items):
select
*
from
artist_item join items on
artist_item.item_id = items.item_id
where
artist_item.artist_id = [ID]
or all artists related to an item (e.g. one item was worked on by several artists):
select
*
from
artist_item join artist on
artist_item.artist_id = artist.artist_id
where
artist_item.item_id = [ID]