I have the following tables:
TABLE country
(
country_id int not null,
name varchar(32) not null,
primary key (country_id),
unique key (name)
);
TABLE place
(
place_id int not null,
name varchar(32) not null,
country_id int not null,
primary key (place_id),
unique key (country_id, name)
);
TABLE image
(
image_id int not null,
title varchar(75) not null,
caption varchar(500) not null,
filename varchar(29) not null,
country_id int,
place_id int,
primary key (image_id),
unique key (filename)
);
An image can have a country (or not) or a place (or not).
Let’s say that I have a record in the country table:
23 | Spain |
And places in the place table:
3 | Madrid | 23
6 | Barcelona | 23
I want to tag an image with the country Spain and a place Barcelona
In the image table, should I enter the country_id and the place_id or just the place_id since the country is already determine by the place_id?
Polymorphic relationships are not easy to model in a relational database.
Here, I would suggest another approach that what you have set up: build two different bridge tables for images - one for countries, and the other for places.
That would look like:
create table countries (
country_id int primary key,
...
);
create table places (
place_id int primary key,
...
);
create table images (
image_id int primary key,
...
);
create table image_countries (
image_id int references images(image_id),
country_id int references countries(country_id),
primary key (image_id, country_id)
);
create table image_places (
image_id int references images(image_id),
place_id int references places(place_id),
primary key (image_id, place_id)
);
You are then free to link a given image to as many places and countries as you like. If you want, instead, to allow just one country and one place per image, you can change the primary key of each bridge table to just image_id.
Change your mental focus. Focus on a "place" being associated with an image. Then make places work for both both "Spain" and "Madrid, Spain". Do not attempt to jump past places to get to countries.
So, in places, allow an empty name with a non-empty country to indicate "Spain".
I would use the standard 2-letter country codes instead of a 4-byte INT. Then I would either display "ES" with the images for Spain or have a lookup table like your current countries.
Related
I have two tables tb_schools(school_id,school_name), tb_programms(pid,p_name,school_id)
If suppose multiple schools offer the same program then how can I design the DB.
I mean can I pass the list of school_ids like[sc1,sc2,sc3] in school_id of tb_programms.
note: I can't add multiple rows for a single program.
If multiple schools offer the same program you need to design your schema differently. The canonical solution would be to have a table for schools, a table for programs, and a mapping table for the programs held at each school. E.g.:
CREATE TABLE tb_schools (
school_id INT AUTO_INCREMENT PRIMARY KEY,
school_name VARCHAR(30) NOT NULL
);
CREATE TABLE tb_programs (
pid INT AUTO_INCREMENT PRIMARY KEY,
p_name VARCHAR(30) NOT NULL
);
CREATE TABLE tb_school_programs
sid INT NOT NULL,
pid INT NOT NULL,
PRIMARY KEY (sid, pid),
FOREIGN KEY (sid) REFERENCES school(school_id),
FOREIGN KEY (pid) REFERENCES programs(pid)
);
I am currently working at a construction management software and I stepped into a problem (described below) when designing the schema for my database which contains four tables, for now, i.e employee, address, user and license.
The tables that created the confusion are employee and address; code provided down below.
In order to maintain the Physical integrity of my data, I did not store anything lexically derived from address, such as Address1, Address2, City, State, County etc in the table employee; since I subjectively considered that it would be better for attributes that do not uniquely identify a specific employee to make part of another table. Now my question arises:
Is it a good choice to use a GUID as the primary key for the address table?
If yes, is there any possibility that it may be a factor which prevents fast querying?
The reason why I have chosen to use a GUID as PK-indexing is that I was left with no options. EmployeeId from address does not offer me a solution since I can not have a field being PK as well as FK: see this.
CREATE TABLE employee(
employeeId INT
NOT NULL
CHECK(employeeId > 0),
firstname VARCHAR(20)
NOT NULL,
lastname VARCHAR(20)
NOT NULL,
sex VARCHAR(1)
NOT NULL,
birthdate DATE
NOT NULL,
addressId VARCHAR(30),
PRIMARY KEY(employeeId)
);
CREATE TABLE address(
addressId VARCHAR(30)
NOT NULL,
employeeId INT,
Address1 VARCHAR(120)
NOT NULL,
Address2 VARCHAR(120),
Address3 VARCHAR(120),
City VARCHAR(100)
NOT NULL,
State CHAR(2)
NOT NULL,
Country CHAR(2)
NOT NULL,
PostalCode VARCHAR(16)
NOT NULL,
PRIMARY KEY(addressId),
FOREIGN KEY(employeeId) REFERENCES employee(employeeId) ON DELETE SET NULL
);
ALTER TABLE employee
ADD FOREIGN KEY(addressId)
REFERENCES address(addressId)
ON DELETE SET NULL;
I would love to know if there are any other ways to create an appropriate relationship between employee and address without using GUID. Another way would be a specified (int) value, but in that case a disadvantage would be:
Introducing by mistake a FK != PK which will lead to a poor relationship between the tables.
EDIT:
Some of you suggested in the comments to change "UUID" indexing to AUTO_INCREMENT but the problem for me arises when I have to insert employees from my WPF-application.
The PK addressId from address will keep increasing on its own. What am I supposed to pass into the FK then to keep the relationship tight and correct then?
Should I create a variable of type int, let's say var i = 0 and every time I insert one employee -> increase that variable by one -> assign it to the FK or?
You'll want something like this in order to do a many-to-many association between employees and addresses (Address types will be like home, work, billing, travel, etc.). You can then create other entity tables to track associations between those entities and addresses as well. In the table employee_address, those columns will be foreign keys back to their relative tables.
As for using GUIDs vs INT for primary keys, numeric values read faster than string values on a JOIN. Depending on how large your data gets, you may need to switch from INT to BIGINT years down the road.
Also, give these people some space to enter their names. 20 characters is way too short, especially for last names.
employee
--------
employee_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
firstname VARCHAR(255) NOT NULL,
lastname VARCAHR(255) NOT NULL,
gender BIT NOT NULL,
birthdate DATE NOT NULL
address
---------
address_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
address1 VARCHAR(255),
address2 VARCHAR(255),
address3 VARCHAR(255),
city VARCHAR(255),
state CHAR(2),
country CHAR(2)
address_type
--------------
address_type_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
address_type VARCHAR(255)
employee_address
-----------------
employee_address_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
employee_id INT,
address_id INT,
address_type_id INT
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."
I am looking to build a sports database where the user selects a country from a list 15 different countries, that then presents different divisions within that country. The user then selects a division and is presented with a list of teams within that division to select.
One approach I had been advised to take is to have separate tables for Countries and Teams.
Table - Countries
Country | Teams (int array)
Table - Teams
ID (int) | Team Name | Division
The 'Teams (int array) would match with the ID of the team name in Teams Table.
I'm not 100% sure this is the correct approach.
What other solutions would be advisable?
You should create three tables. Something like
create table countries(
id int unsigned auto_increment primary key,
name varachar(100) not null,
unique key (name)
);
create table divisions(
id int unsigned auto_increment primary key,
country_id int unsigned not null,
name varchar(100) not null,
unique key uk_name (name),
foreign key fk_countries (country_id)
references countries(id)
on update restrict
on delete cascade
);
create table teams(
id int unsigned auto_increment primary key,
division_id int unsigned not null,
name varchar(100) not null,
unique key uk_name (name),
foreign key fk_divisions (division_id)
references divisions(id)
on update restrict
on delete cascade
);
Your queries would be..
Show all countries:
select id, name from countries;
After the user selects a country you know the ID of that country and you can show all divisions from that country:
select id, name from divisions where county_id = ?;
Now the user selects a division and you get its ID - so you can show all teams from that division:
select id, name from teams where division_id = ?;
You don't need to store the teams in the country table. Just have a countries table with the names of the countries. In the teams table, have an column for the country which uses the index of the country table. Now you can search for all teams in a given country. For example;
select teamName, otherTeamData from teamTable where countryIndex = selectedCountryIndex and divisionIndex = selectedDivisionIndex;
You also need a divisions table which will likely have a countryIndex as well. Then you present a list of divisions specific to any country.
this question may asked by several people.But still I didn't get a right answer. So here it is
I want to load Continents, Countries and Cities to below combo boxes. So I need to make relation between those. Means city need be in a country and country belongs to a continent. So of course I am talking about a tree structure. So how should I implement this in MYSQL database in efficient way? How many tables need? How to do the relation among those?
Here is the image.
http://i.stack.imgur.com/st4oz.jpg
There are formal ways to represent arbitrary trees, but I think the following is simpler and should be sufficient:
CREATE TABLE Continents (
id INT AUTO_INCREMENT NOT NULL,
name VARCHAR(20) NOT NULL,
PRIMARY KEY (id),
UNIQUE (name)
)
CREATE TABLE Countries (
id INT AUTO_INCREMENT NOT NULL,
name VARCHAR(20) NOT NULL,
continent INT NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (continent) REFERENCES Continents(id),
UNIQUE (name)
)
CREATE TABLE Cities (
id INT AUTO_INCREMENT NOT NULL,
name VARCHAR(20) NOT NULL,
country INT NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (country) REFERENCES Countries(id),
UNIQUE (name)
)
I didn't test the code, so there may be some syntax errors. I hope the intent is clear, though.
#Vmai raises an excellent point in his comment on the question.
My solution would be to have the "problem countries" once in the Countries table for every continent they are in. (So Turkey would be twice in the database, once with continent set to the id of Asia, and once to the id of Europe. Same for Russia.) The same goes for cities, of course.
All you need is one table. As long as the structure of data is the same, there's no reason to have three tables. And this way, you don't care about the depth and how the locations administratively are organized.
+-------------------------+
| locations |
+-------------------------+
| location_id (int) | primary key
| location_name (varchar) |
| parent_id (int) | index
+-------------------------+
Or another solution
+-------------------------+
| locations |
+-------------------------+
| location_id (int) | primary key
| location_name (varchar) |
| left (int) |
| right (int) |
+-------------------------+
To be explained...