Enforce composite unique constraint that depends on parent column value - mysql

With provided schema i want to somehow enforce that there is unique reserved_seat:seat_id per showing. In other words you can't reserve specific seat if it is already reserved in that showing.
One option is to also add showing_id to reservation_seat (which is redundant) and then make unique constraint on (showing_id, seat_id).
Can this be done in sql or it falls to application code?
The DDL:
CREATE TABLE showing
(
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(45) NOT NULL,
PRIMARY KEY (id)
)
CREATE TABLE reservation
(
id INT NOT NULL AUTO_INCREMENT,
showing_id INT NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (showing_id) REFERENCES showing(id)
)
CREATE TABLE reservation_seat
(
id INT NOT NULL AUTO_INCREMENT,
reservation_id INT NOT NULL,
seat_id INT NOT NULL,
confirmed TINYINT,
PRIMARY KEY (id),
FOREIGN KEY (reservation_id) REFERENCES reservation(id),
FOREIGN KEY (seat_id) REFERENCES seat(id)
)
CREATE TABLE seat
(
id INT NOT NULL AUTO_INCREMENT,
row VARCHAR(45) NOT NULL,
column VARCHAR(45) NOT NULL,
PRIMARY KEY (id)
)

I believe that this is one of those rare cases where the use of surrogate keys (auto_increment id's) instead of natural keys has led you astray. Consider how your table definitions would look if you used natural keys instead:
CREATE TABLE showing
(
name VARCHAR(45) NOT NULL, -- globally unique
PRIMARY KEY (name)
)
CREATE TABLE reservation
(
showing_name VARCHAR(45) NOT NULL,
name VARCHAR(45) NOT NULL, -- only unique within showing_name
PRIMARY KEY (name, showing_name),
FOREIGN KEY (showing_name) REFERENCES showing(name)
)
CREATE TABLE reservation_seat
(
showing_name VARCHAR(45) NOT NULL,
reservation_name VARCHAR(45) NOT NULL,
seat_row VARCHAR(45) NOT NULL,
seat_column VARCHAR(45) NOT NULL,
confirmed TINYINT,
PRIMARY KEY (showing_name, reservation_name, seat_row, seat_column),
FOREIGN KEY (showing_name, reservation_name) REFERENCES reservation(showing_name, name),
FOREIGN KEY (seat_row, seat_column) REFERENCES seat(row, column)
)
Now you can add your reserved seat per showing constraint as an Alternate Key on reservation_seat:
CREATE TABLE reservation_seat
(
showing_name VARCHAR(45) NOT NULL,
reservation_name VARCHAR(45) NOT NULL,
seat_row VARCHAR(45) NOT NULL,
seat_column VARCHAR(45) NOT NULL,
confirmed TINYINT,
PRIMARY KEY (showing_name, reservation_name, seat_row, seat_column),
FOREIGN KEY (showing_name, reservation_name) REFERENCES reservation(showing_name, name),
FOREIGN KEY (seat_row, seat_column) REFERENCES seat(row, column),
CONSTRAINT UC_seat_showing_reserved UNIQUE(showing_name, seat_row, seat_column)
)
However, this makes it clear that the primary key is superfluous because it's just a weaker version of the constraint that we have added, so we should replace it with our new constraint.
CREATE TABLE reservation_seat
(
showing_name VARCHAR(45) NOT NULL,
reservation_name VARCHAR(45) NOT NULL,
seat_row VARCHAR(45) NOT NULL,
seat_column VARCHAR(45) NOT NULL,
confirmed TINYINT,
PRIMARY KEY (showing_name, seat_row, seat_column),
FOREIGN KEY (showing_name, reservation_name) REFERENCES reservation(showing_name, name),
FOREIGN KEY (seat_row, seat_column) REFERENCES seat(row, column)
)
We may worry now that our reservation_seat could be referencing a reservation with a different showing_id than the reservation_seat itself, but that's not a problem for natural keys because the first foreign key reference prevents that.
Now all we need to do is to translate this back into surrogate keys:
CREATE TABLE reservation_seat
(
id INT NOT NULL AUTO_INCREMENT,
showing_id INT NOT NULL,
reservation_id INT NOT NULL,
seat_id INT NOT NULL,
confirmed TINYINT,
PRIMARY KEY (id),
FOREIGN KEY (showing_id, reservation_id) REFERENCES reservation(showing_id, id),
FOREIGN KEY (seat_id) REFERENCES seat(id),
CONSTRAINT UC_seat_showing_reserved UNIQUE(showing_id, seat_id)
)
Because we're making the reservation_seat(id) the primary key, we have to change the named PK definition back into a unique constraint. Compared to your original reservation_seat definition, we end up with showing_id added, but with the modified stronger first foreign key definition we now insure both that reservation_seat are unique within a showing and that reservation_seat cannot have a showing_id different from its parent reservation.
(Note: you will probably have to quote the 'row' and 'column' column names in the SQL code above)
Additional Note: DBMS's vary on this (and I am not sure about MySql in this case), but many will require that a Foreign Key relation have a corresponding Primary Key or Unique Constraint on the target (referenced) table. This would mean that you would have to alter the reservation table with a new constraint like:
CONSTRAINT UC_showing_reserved UNIQUE(showing_id, id)
to match the new FK definition on reservation_seat that I suggested above:
FOREIGN KEY (showing_id, reservation_id) REFERENCES reservation(showing_id, id),
Technically, this would be a redundant constraint since it is a weaker version of the primary key on the reservation table, but in this case SQL would probably still require it to implement the FK.

Does it take 90 characters to specify a "seat"? The seats I am familiar with are like "103-45" or "J17". Or even "Sec 4 Row 43 Seat 105". You have not mentioned it, but row/column is not adequate to answer the question "are these two seats adjacent?"
My first approach to the problem is to get rid of the table seat, other than being able to enumerate all the seats in a venue.
Then I would question the table reservation_seat, which smells like a many-to-many mapping (plus a flag). Many:many implies non uniqueness. So, something has to give.
The raw, unnormalized, data seems to be
showing: showing_id (PK), date, time, location
reservation: showing_id, seat, confirmed
Having this (on reservation) probably answers your question:
PRIMARY KEY(showing_id, seat)
It ties the two tables together, provides a 'natural' PK, and still allows for the confirmed flag.
I don't know your logic for "confirming". I assume that you cannot reassign a seat while it is waiting to be confirmed?
Back to my starting comment. seat VARCHAR(15) might be appropriate. And, if you need it, another table could have
CREATE TABLE venue (
venue_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR (144) NOT NULL,
location ...
capacity SMALLINT UNSIGNED NOT NULL,
...
PRIMARY KEY(venue_id)
) ENGINE=InnoDB
CREATE TABLE seat (
venue_id SMALLINT UNSIGNED NOT NULL,
seat_num VARCHAR(30) NOT NULL,
is_handicap ...,
strip_num SMALLINT UNSIGNED NOT NULL, -- see below
PRIMARY KEY(venue_id, seat_num)
) ENGINE=InnoDB
This fails to take care of a venue where you sometimes want to block off the balcony, thereby invalidating some of the seats. Having a different venue and id with most of the info the same might be direction to take.
Be sure to use transactions (BEGIN..COMMIT and FOR UPDATE) where appropriate.
CREATE TABLE showing (
showing_id MEDIUM UNSIGNED NOT NULL AUTO_INCREMENT,
venue_id SMALLINT UNSIGNED NOT NULL,
date ...
notes ...
PRIMARY KEY(showing_id)
) ENGINE=InnoDB
To deal with seat adjacency, I suggest manually assign each stretch of adjacent seats a "strip" number that stops at aisles, posts, etc. Alas, that is not adequate for a middle section where seat "1" is in the middle, with even numbers going one way and odd going the other way. So K-8 and K-9 are quite far apart, but K-8 and K-10 are adjacent, in spite sorting far apart.
As for confirmed, it "belongs" in reservation. But it might be more convenient for other actions to have it in seat. We may need to work out the SQL statements to make that decision. Also, the SQL statements are necessary for deciding on what secondary INDEXes to have.

Related

MySQL - Many-To-Many Relationship between three entities

I am having trouble figuring out the best design for my many-to-many relationship in my database. My project allows users to create what we are calling log alarms. A log alarm will check if a given log meets certain criteria and, if so, it will send a message to an AWS SNS topic. What I want to do is relate log alarms to AWS SNS topics. I also want to relate which user assigned that log alarm to that AWS SNS topic.
I have a table class XRefUserLogAlarmSNSTopic. It has three foreign keys. The goal of this table is to relate which SNS topics are related to what log alarms and to indicate which user made the relation. This seems rather messy to me and I get all sorts of errors when I try to create new log alarms or join tables in Spring JPA. My question is, is there are better database structure for what I am trying to achieve
UserId INT NOT NULL AUTO_INCREMENT,
Username VARCHAR(50) NOT NULL,
Password TEXT NOT NULL,
Email VARCHAR(255) NOT NULL,
Dashboard LONGTEXT NOT NULL,
PRIMARY KEY (UserId),
UNIQUE (Username),
UNIQUE (Email)
);
CREATE TABLE SNSTopics (
SNSTopicId INT NOT NULL AUTO_INCREMENT,
TopicName VARCHAR(50) NOT NULL,
TopicArn VARCHAR(255) NOT NULL,
PRIMARY KEY (SNSTopicId),
UNIQUE (TopicName),
UNIQUE (TopicArn)
);
CREATE TABLE LogGroups (
LogGroupId INT NOT NULL AUTO_INCREMENT,
Name VARCHAR(255) NOT NULL,
PRIMARY KEY (LogGroupId),
UNIQUE (Name)
);
CREATE TABLE Keywords (
KeywordId INT NOT NULL AUTO_INCREMENT,
Word VARCHAR(70),
PRIMARY KEY (KeywordId),
UNIQUE (Word)
);
CREATE TABLE LogAlarms (
LogAlarmId INT NOT NULL AUTO_INCREMENT,
LogLevel VARCHAR(5) NOT NULL CHECK (LogLevel IN ('TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR')),
Comparison VARCHAR(2) CHECK (Comparison IN ('==', '<', '<=', '>', '>=')),
AlarmName VARCHAR(255) NOT NULL,
KeywordRelationship CHAR(3) CHECK (KeywordRelationship IN ('ANY', 'ALL', NULL)),
PRIMARY KEY (LogAlarmId),
UNIQUE (AlarmName)
);
CREATE TABLE MetricAlarms (
MetricAlarmId INT NOT NULL AUTO_INCREMENT,
AlarmArn VARCHAR(100) NOT NULL,
PRIMARY KEY (MetricAlarmId),
UNIQUE (AlarmArn)
);
CREATE TABLE XRefUserMetricAlarm (
UserMetricAlarmId INT NOT NULL AUTO_INCREMENT,
UserId INT NOT NULL,
MetricAlarmId INT NOT NULL,
PRIMARY KEY (UserMetricAlarmId),
FOREIGN KEY (UserId) REFERENCES Users(UserId) ON DELETE CASCADE,
FOREIGN KEY (MetricAlarmId) REFERENCES MetricAlarms(MetricAlarmId) ON DELETE CASCADE,
UNIQUE (UserId, MetricAlarmId)
);
CREATE TABLE XRefLogAlarmLogGroup (
LogAlarmLogGroupId INT NOT NULL AUTO_INCREMENT,
LogAlarmId INT NOT NULL,
LogGroupId INT NOT NULL,
PRIMARY KEY (LogAlarmLogGroupId),
FOREIGN KEY (LogAlarmId) REFERENCES LogAlarms(LogAlarmId) ON DELETE CASCADE,
FOREIGN KEY (LogGroupId) REFERENCES LogGroups(LogGroupId) ON DELETE CASCADE,
UNIQUE (LogAlarmId, LogGroupId)
);
CREATE TABLE XRefLogAlarmKeyword (
LogAlarmKeywordId INT NOT NULL AUTO_INCREMENT,
LogAlarmId INT NOT NULL,
KeywordId INT NOT NULL,
PRIMARY KEY (LogAlarmKeywordId),
FOREIGN KEY (LogAlarmId) REFERENCES LogAlarms(LogAlarmId) ON DELETE CASCADE,
FOREIGN KEY (KeywordId) REFERENCES Keywords(KeywordId) ON DELETE CASCADE,
UNIQUE (LogAlarmId, KeywordId)
);
CREATE TABLE XRefUserLogAlarmSNSTopic (
UserLogAlarmSNSTopicId INT NOT NULL AUTO_INCREMENT,
LogAlarmId INT NOT NULL,
SNSTopicId INT NOT NULL,
UserId INT NOT NULL,
PRIMARY KEY (UserLogAlarmSNSTopicId),
FOREIGN KEY (LogAlarmId) REFERENCES LogAlarms(LogAlarmId) ON DELETE CASCADE,
FOREIGN KEY (SNSTopicId) REFERENCES SNSTopics(SNSTopicId) ON DELETE CASCADE,
FOREIGN KEY (UserId) REFERENCES Users(UserId) ON DELETE CASCADE,
UNIQUE (LogAlarmId, SNSTopicId, UserId)
);```
To match your description, your XRefUserLogAlarmSNSTopic is not correct.
You do not actually want to link three entities, just two: you want to relate log alarms to AWS SNS topics (which are the two values that identify that relationship), and then add a user as an attribute to that relation. Although in this case this specific attribute refers to another entity, it is logically not fundamentally different than e.g. a timestamp that stores when that relationsship was created (by that user).
The difference to your current table is the primary key/unique key: your current table allows you to add a relationship between an alarm and a topic several times if different users add them, as only (alarm, topic, user) needs to be unique, instead of (alarm, topic) being unique, and user being an attribute to that relation.
I would also consider if you want an ON DELETE CASCADE for the UserId column. While cascading for LogAlarmId and SNSTopicId makes sense, it's not obvious that you need to remove all relations when you delete the user that created them (although it of course depends on your requirements). It may be better to just set them to null. So I would propose a table like
CREATE TABLE XRefLogAlarmSNSTopic ( -- user not part of table name
LogAlarmSNSTopicId INT NOT NULL AUTO_INCREMENT,
LogAlarmId INT NOT NULL,
SNSTopicId INT NOT NULL,
UserId INT NULL, -- null
PRIMARY KEY (UserLogAlarmSNSTopicId),
FOREIGN KEY (LogAlarmId) REFERENCES LogAlarms(LogAlarmId) ON DELETE CASCADE,
FOREIGN KEY (SNSTopicId) REFERENCES SNSTopics(SNSTopicId) ON DELETE CASCADE,
FOREIGN KEY (UserId) REFERENCES Users(UserId) ON DELETE SET NULL, -- set null
UNIQUE (LogAlarmId, SNSTopicId) -- user not part of primary key candidate
)
It is obviously also possible that you want that 3-way-relationship, e.g. each user creates their own alarm-topic-relations, in which case your table would be correct, just your description would not be precise enough.

Is it good to use UNIQUE index?

I have a register of students, the main table is this one and I just wanna make sure there won't be students with duplicated names, emails, etc. ID is the main key so I don't need to use a PK.
CREATE TABLE student(
id INT UNSIGNED NOT NULL,
campus_id SMALLINT UNSIGNED NOT NULL,
first_career_id SMALLINT UNSIGNED NOT NULL,
second_career_id SMALLINT UNSIGNED NULL,
name VARCHAR(70) NOT NULL,
password CHAR(60) NOT NULL,
email VARCHAR(100) NOT NULL,
phone TINYINT UNSIGNED NULL,
UNIQUE(id),
UNIQUE(name),
UNIQUE(email),
UNIQUE(phone),
FOREIGN KEY(first_career_id)
REFERENCES career(id)
ON DELETE CASCADE,
FOREIGN KEY(second_career_id)
REFERENCES career(id)
ON DELETE CASCADE
) ENGINE=INNODB;
I could validate those fields when inserting them through the applicacion, which way is better?
If uniqueness is a domain constraint (which it is in your example), then create a UNIQUE constraint on the columns(s) (which creates a unique index for you).
ALTER TABLE student
ADD CONSTRAINT ux_student_email
UNIQUE (email)
If you can be 100% sure (and future proof) that no other application will insert data, then ensuring uniqueness in the application is fine. If you value correctness, add a constraint at the database level in addition to checking in the application.

Usage of MySQL foreign key referencing multiple columns

I just stumbled across possibility of MySQL foreign key to reference multiple columns. I would like to know what is main purpose of multi-column foreign keys like shown bellow
ALTER TABLE `device`
ADD CONSTRAINT `fk_device_user`
FOREIGN KEY (`user_created_id` , `user_updated_id` , `user_deleted_id`)
REFERENCES `user` (`id` , `id` , `id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION;
My questions are
Is it the same as creating three independent foreign keys?
Are there any pros / cons of using one or another?
What is the exact use-case for this? (main question)
Is it the same as creating three independent foreign keys?
No. Consider the following.
First off, it is not useful to think of it as (id,id,id), but rather (id1,id2,id3) in reality. Because a tuple of (id,id,id) would have no value over just a single column index on id. As such you will see the schema below that depicts that.
create schema FKtest001;
use FKtest001;
create table user
( id int auto_increment primary key,
fullname varchar(100) not null,
id1 int not null,
id2 int not null,
id3 int not null,
index `idkUserTuple` (id1,id2,id3)
);
create table device
( id int auto_increment primary key,
something varchar(100) not null,
user_created_id int not null,
user_updated_id int not null,
user_deleted_id int not null,
foreign key `fk_device_user` (`user_created_id` , `user_updated_id` , `user_deleted_id`)
REFERENCES `user` (`id1` , `id2` , `id3`)
);
show create table device;
CREATE TABLE `device` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`something` varchar(100) NOT NULL,
`user_created_id` int(11) NOT NULL,
`user_updated_id` int(11) NOT NULL,
`user_deleted_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `fk_device_user` (`user_created_id`,`user_updated_id`,`user_deleted_id`),
CONSTRAINT `device_ibfk_1` FOREIGN KEY (`user_created_id`, `user_updated_id`, `user_deleted_id`) REFERENCES `user` (`id1`, `id2`, `id3`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
show indexes from device; -- shows 2 indexes (a PK, and composite BTREE)
-- FOCUS heavily on the `Seq_in_index` column for the above
-- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
drop table device;
drop table user;
create table user
( id int auto_increment primary key,
fullname varchar(100) not null,
id1 int not null,
id2 int not null,
id3 int not null,
index `idkUser1` (id1),
index `idkUser2` (id2),
index `idkUser3` (id3)
);
create table device
( id int auto_increment primary key,
something varchar(100) not null,
user_created_id int not null,
user_updated_id int not null,
user_deleted_id int not null,
foreign key `fk_device_user1` (`user_created_id`)
REFERENCES `user` (`id1`),
foreign key `fk_device_user2` (`user_updated_id`)
REFERENCES `user` (`id2`),
foreign key `fk_device_user3` (`user_deleted_id`)
REFERENCES `user` (`id3`)
);
show create table device;
CREATE TABLE `device` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`something` varchar(100) NOT NULL,
`user_created_id` int(11) NOT NULL,
`user_updated_id` int(11) NOT NULL,
`user_deleted_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `fk_device_user1` (`user_created_id`),
KEY `fk_device_user2` (`user_updated_id`),
KEY `fk_device_user3` (`user_deleted_id`),
CONSTRAINT `device_ibfk_1` FOREIGN KEY (`user_created_id`) REFERENCES `user` (`id1`),
CONSTRAINT `device_ibfk_2` FOREIGN KEY (`user_updated_id`) REFERENCES `user` (`id2`),
CONSTRAINT `device_ibfk_3` FOREIGN KEY (`user_deleted_id`) REFERENCES `user` (`id3`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
show indexes from device; -- shows 4 indexes (a PK, and 3 indiv FK indexes)
-- FOCUS heavily on the `Seq_in_index` column for the above
There are 2 sections there. The show indexes from device will show the difference of, in the top part, 2 indexes maintained. In the bottom part, 4 indexes maintained. If for some reason the index tuple in the top part is useful for the system, then that tuple approach is certainly the way to go.
The reason is the following. The tuple exists as a group. Think of it as an instance of a set that has meaning as a group. Compare that to the mere existence of the individual parts, and there is a difference. It is not that the users exist, it is that there is a user row that has that tuple as an existence.
Are there any pros / cons of using one or another?
The pros were described above in the last paragraph: existence as an actual grouping in the user table as a tuple.
They are apple and oranges and used for different purposes.
What is the exact use-case for this? (main question)
A use case would be something that requires the existence of the tuple as a group, as opposed to the existence of the individual items. It is used for what is called compositing. Compositing FK's in particular. See this answer of mine Here as one case.
In short, it is when you want to enforce special hard to think of solutions that require Referential Integrity (RI) at a composited level (groupings) of other entities. Many people think it can't be done so they first think TRIGGER enforcement or front-end Enforcement. Fortunately those use cases can be achieved via the FK Composites thus leaving RI at the db level where it should be (and never at the front-end).
Addendum
Request from OP for a better real life example than the link above.
Consider the following schema:
CREATE SCHEMA testRealLifeTuple;
USE testRealLifeTuple;
CREATE TABLE contacts
( id INT AUTO_INCREMENT PRIMARY KEY,
fullname VARCHAR(100) NOT NULL
-- etc
);
CREATE TABLE tupleHolder
( -- a tuple representing a necessary Three-some validation
-- and vetting to get financing
--
-- If you can't vett these 3, you can't have my supercomputer financed
--
id INT AUTO_INCREMENT PRIMARY KEY,
CEO INT NOT NULL, -- Chief Executive Officer
CFO INT NOT NULL, -- Chief Financial Officer
CIO INT NOT NULL, -- Chief Geek
creditWorthiness INT NOT NULL, -- 1 to 100. 100 is best
-- the unique index is necessary for the device FK to succeed
UNIQUE INDEX `idk_ContactTuple` (CEO,CFO,CIO), -- No duplicates ever. Good for re-use
FOREIGN KEY `fk_th_ceo` (`CEO`) REFERENCES `contacts` (`id`),
FOREIGN KEY `fk_th_cfo` (`CFO`) REFERENCES `contacts` (`id`),
FOREIGN KEY `fk_th_cio` (`CIO`) REFERENCES `contacts` (`id`)
);
CREATE TABLE device
( -- An Expensive Device, typically our Supercomputer that requires Financing.
-- This device is so wildly expense we want to limit data changes
--
-- Note that the GRANTS (privileges) on this table are restricted.
--
id INT AUTO_INCREMENT PRIMARY KEY,
something VARCHAR(100) NOT NULL,
CEO INT NOT NULL, -- Chief Executive Officer
CFO INT NOT NULL, -- Chief Financial Officer
CIO INT NOT NULL, -- Chief Geek
FOREIGN KEY `fk_device_2_tuple` (`CEO` , `CFO` , `CIO`)
REFERENCES `tupleHolder` (`CEO` , `CFO` , `CIO`)
--
-- Note that the GRANTS (privileges) on this table are restricted.
--
);
DROP SCHEMA testRealLifeTuple;
The highlights of this schema come down to the UNIQUE KEY in tupleHolder table, the FK in device, the GRANT restriction (grants not shown), and the fact that the device is shielded from tomfoolery edits in the tupleHolder because of, as mentioned:
GRANTS
That the FK must be honored, so the tupleHolder can't be messed with
If the tupleHolder was messed with (the 3 contacts ids), then the FK would be violated.
Said another way, it is NO WAY the same as the device having an FK based on a single column in device, call it [device.badIdea INT], that would FK back to tupleHolder.id.
Also, as mentioned earlier, this differs from merely having the contacts exist. Rather, it matters that the composition of contacts exists, it is a tuple. And in our case the tuple has been vetted, and has a credit worthiness rating, and the id's in that tuple can't be messed with, after a device is bought, unless sufficient GRANTS allow it. And even then, the FK is in place.
It may take 15 minutes for that to sink in, but there is a Huge difference.
I hope this helps.

Error during the creation of table due to foreign key

I have table1 already in my db.
Table1:
CREATE TABLE `product` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`typename` varchar(255) DEFAULT NULL,
`typecode` varchar(55) DEFAULT NULL,
`parent1` int(11) DEFAULT NULL,
`parent2` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `parent1` (`parent1`),
KEY `parent2` (`parent2`)
) ENGINE=InnoDB AUTO_INCREMENT=396 DEFAULT CHARSET=latin1;
I tried to create the second table with foreign key which has reference to product.typename
this is the creation query I have used.
CREATE TABLE measurements (
id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
age_group varchar(20) NOT NULL,
article_type varchar(255) DEFAULT NULL,
dimension text ,
createdOn int(11) NOT NULL,
updatedOn int(11) NOT NULL,
createdBy text NOT NULL,
foreign KEY(article_type) references product(typename)
)ENGINE=InnoDB AUTO_INCREMENT=396 DEFAULT CHARSET=latin1;
But this table creation is a failure with the following error.
ERROR 1215 (HY000): Cannot add foreign key constraint
I have done show engine innodb\g
------------------------
LATEST FOREIGN KEY ERROR
------------------------
2015-05-15 19:03:28 131f71000 Error in foreign key constraint of table db/measurements:
foreign KEY(article_type) references product(typename)
)ENGINE=InnoDB AUTO_INCREMENT=396 DEFAULT CHARSET=latin1:
Cannot find an index in the referenced table where the
referenced columns appear as the first columns, or column types
in the table and the referenced table do not match for constraint.
Note that the internal storage type of ENUM and SET changed in
tables created with >= InnoDB-4.1.12, and such columns in old tables
cannot be referenced by such columns in new tables.
See http://dev.mysql.com/doc/refman/5.6/en/innodb-foreign-key-constraints.html
for correct foreign key definition.
Can some one point me the problem and what is this first columns concept?
Referenced column should be Primary key. Here
foreign KEY(article_type) references product(typename)
you want to reference with typename column which is not PK.
To do it in properly way you should create table ProductType like this:
CREATE TABLE `ProductType` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`typename` varchar(255) DEFAULT NULL,
`typecode` varchar(55) DEFAULT NULL,
) ENGINE=InnoDB AUTO_INCREMENT=396 DEFAULT CHARSET=latin1;
then you can create reference like this:
CREATE TABLE measurements (
id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
age_group varchar(20) NOT NULL,
IdProductType NOT NULL,
dimension text ,
createdOn int(11) NOT NULL,
updatedOn int(11) NOT NULL,
createdBy text NOT NULL,
foreign KEY(IdProductType) references ProductType(Id)
)ENGINE=InnoDB AUTO_INCREMENT=396 DEFAULT CHARSET=latin1;
Don't forget to do it with Product table.
Above solution is only suggestion, you have to consider your table structure yourself.
A foreign key references a key. This is usually the primary key, but doesn't have to be. In your case however you reference a column (typename) which is not defined as a key. This shows a design flaw.
You decided to use technical IDs as primary keys for your tables. You can do this. But if you do this, keep two things in mind:
You've created IDs in order to link tables easily. So don't reference a record by another column (such as typename), but by its ID.
You must still make sure that the table's natural key is unique.
As to point 2: What is your table's natural key? What is or are the fields that uniquely identify a record (apart from your technically created ID)? Is it typename? Is typename the product's name and must it be unique? Or is this typecode? Whatever it is, give this field a unique constraint, so you cannot have the same product twice in your table.
Maybe it would help you learn to design your database, if you didn't use technical IDs at all. Give it a thought.
Just a side note: Be aware that MySQL has a strange way of using the keyword KEY:
create table t (col int key);
Here KEY really means the table's primary key. col cannot be null and col must be unique. It is short for:
create table t (col int primary key);
However,
create table t (col int, key(col));
is something entirely else. Here, KEY is not short for PRIMARY KEY, but a synonym for INDEX. col can be null, col doesn't have to be unique. So better use the synonym INDEX to make it clear to a reader:
create table t (col int, index(col));
When working with an additional ID, as you are doing, you even need a unique index:
create table t (id int primary key, col int, unique index(col));
or
create table t (id int, col int, primary key(id), unique index(col));

Can a table have two foreign keys?

I have the following tables (Primary key in bold. Foreign key in Italic)
Customer table
ID---Name---Balance---Account_Name---Account_Type
Account Category table
Account_Type----Balance
Customer Detail table
Account_Name---First_Name----Last_Name---Address
Can I have two foreign keys in the Customer table and how can I implement this in MySQL?
Updated
I am developing a web based accounting system for a final project.
Account Category
Account Type--------------Balance
Assets
Liabilities
Equity
Expenses
Income
Asset
Asset_ID-----Asset Name----Balance----Account Type
Receivable
Receivable_ID-----Receivable Name-------Address--------Tel-----Asset_ID----Account Type
Receivable Account
Transaction_ID----Description----Amount---
Balance----Receivable_ID----Asset_ID---Account Type
I drew the ER(Entity relationship) diagram using a software and when I specify the relationship it automatically added the multiple foreign keys as shown above. Is the design not sound enough?
create table Table1
(
id varchar(2),
name varchar(2),
PRIMARY KEY (id)
)
Create table Table1_Addr
(
addid varchar(2),
Address varchar(2),
PRIMARY KEY (addid)
)
Create table Table1_sal
(
salid varchar(2),`enter code here`
addid varchar(2),
id varchar(2),
PRIMARY KEY (salid),
index(addid),
index(id),
FOREIGN KEY (addid) REFERENCES Table1_Addr(addid),
FOREIGN KEY (id) REFERENCES Table1(id)
)
Yes, MySQL allows this. You can have multiple foreign keys on the same table.
Get more details here FOREIGN KEY Constraints
The foreign keys in your schema (on Account_Name and Account_Type) do not require any special treatment or syntax. Just declare two separate foreign keys on the Customer table. They certainly don't constitute a composite key in any meaningful sense of the word.
There are numerous other problems with this schema, but I'll just point out that it isn't generally a good idea to build a primary key out of multiple unique columns, or columns in which one is functionally dependent on another. It appears that at least one of these cases applies to the ID and Name columns in the Customer table. This allows you to create two rows with the same ID (different name), which I'm guessing you don't want to allow.
Yes, a table have one or many foreign keys and each foreign keys hava a different parent table.
CREATE TABLE User (
user_id INT NOT NULL AUTO_INCREMENT,
userName VARCHAR(100) NOT NULL,
password VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
userImage LONGBLOB NOT NULL,
Favorite VARCHAR(255) NOT NULL,
PRIMARY KEY (user_id)
);
and
CREATE TABLE Event (
EventID INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (EventID),
EventName VARCHAR(100) NOT NULL,
EventLocation VARCHAR(100) NOT NULL,
EventPriceRange VARCHAR(100) NOT NULL,
EventDate Date NOT NULL,
EventTime Time NOT NULL,
EventDescription VARCHAR(255) NOT NULL,
EventCategory VARCHAR(255) NOT NULL,
EventImage LONGBLOB NOT NULL,
index(EventID),
FOREIGN KEY (EventID) REFERENCES User(user_id)
);