MySQL issue.. creating and referencing tables at once - mysql

I was trying to create multiple tables (some of them referncing the other tables) at once.
I think I matched data types and set primary/foreign keys correctly. But I can only see an error
of 'You cannot add foreign key constraint'. I thought the referenced tables might be created on the first before other tables refernce it, so I reversed the order and the result was the same.
Lastly I tried creating and executing only the referenced tables first(item_type), then referencing tables(item) later.. and... it worked!
However, I wonder if those codes can be executed at once.
Here is code below..
(just two tables are shown to make it simple..)
CREATE TABLE item (
i_id SMALLINT unsigned NOT NULL AUTO_INCREMENT,
i_name VARCHAR(30) NOT NULL,
t_id SMALLINT unsigned NOT NULL,
PRIMARY KEY (i_id),
FOREIGN KEY (t_id) REFERENCES item_type(t_id)
) ENGINE=INNODB;
CREATE TABLE item_type (
t_id SMALLINT unsigned NOT NULL AUTO_INCREMENT,
t_name VARCHAR(20) NOT NULL,
PRIMARY KEY(t_id)
);

You can't define a foreign key to a table that doesn't exist, so doing the CREATE TABLE operations in the order above is not going to work. If you create the item_type table first, then the item table with the foreign key to item_type, it should work.

Database engines execute sql code in batches, so one statement that crate table is one batch, but in you example, first batch references second batch which is not executed yet, so change order of batches and it will be working.
CREATE TABLE item_type (
t_id SMALLINT unsigned NOT NULL AUTO_INCREMENT,
t_name VARCHAR(20) NOT NULL,
PRIMARY KEY(t_id)
);
CREATE TABLE item (
i_id SMALLINT unsigned NOT NULL AUTO_INCREMENT,
i_name VARCHAR(30) NOT NULL,
t_id SMALLINT unsigned NOT NULL,
PRIMARY KEY (i_id),
FOREIGN KEY (t_id) REFERENCES item_type(t_id)
) ENGINE=INNODB;

Related

Enforce composite unique constraint that depends on parent column value

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.

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));

In MySQL how does one create global/general foreign IDs?

Question:
Is there a way to make the foreign ID point to something more generic than one specific table?
Details:
Often I run into the situation where I have several tables which have nothing to do with each other, but still need a common table (in below examples engine is innodb)
CREATE TABLE IF NOT EXISTS movies
(
id INT NOT NULL auto_increment,
name VARCHAR(100) NOT NULL ,
PRIMARY KEY(id)
);
CREATE TABLE IF NOT EXISTS books
(
id INT NOT NULL auto_increment,
name VARCHAR(100) NOT NULL ,
PRIMARY KEY(id)
);
CREATE TABLE IF NOT EXISTS songs
(
id INT NOT NULL auto_increment,
name VARCHAR(100) NOT NULL ,
PRIMARY KEY(id)
);
CREATE TABLE IF NOT EXISTS news_papers
(
id INT NOT NULL auto_increment,
name VARCHAR(100) NOT NULL ,
PRIMARY KEY(id)
);
CREATE TABLE IF NOT EXISTS scrolls
(
id INT NOT NULL auto_increment,
name VARCHAR(100) NOT NULL ,
PRIMARY KEY(id)
);
CREATE TABLE IF NOT EXISTS sumarian_wheat_tablets
(
id INT NOT NULL auto_increment,
name VARCHAR(100) NOT NULL ,
PRIMARY KEY(id)
);
Now I want to keep a record of every time each is viewed like so
CREATE TABLE IF NOT EXISTS movie_history
(
id INT NOT NULL auto_increment,
foreign_id INT NOT NULL ,
view_date TIMESTAMP DEFAULT now(),
FOREIGN KEY (foreign_id) REFERENCES movies ( id ),
PRIMARY KEY(id)
);
CREATE TABLE IF NOT EXISTS book_history
(
id INT NOT NULL auto_increment,
foreign_id INT NOT NULL ,
view_date TIMESTAMP DEFAULT now(),
FOREIGN KEY (foreign_id) REFERENCES books ( id ),
PRIMARY KEY(id)
);
CREATE TABLE IF NOT EXISTS song_history
(
id INT NOT NULL auto_increment,
foreign_id INT NOT NULL ,
view_date TIMESTAMP DEFAULT now(),
FOREIGN KEY (foreign_id) REFERENCES songs ( id ),
PRIMARY KEY(id)
);
CREATE TABLE IF NOT EXISTS news_paper_history
(
id INT NOT NULL auto_increment,
foreign_id INT NOT NULL ,
view_date TIMESTAMP DEFAULT now(),
FOREIGN KEY (foreign_id) REFERENCES news_papers ( id ),
PRIMARY KEY(id)
);
CREATE TABLE IF NOT EXISTS scroll_history
(
id INT NOT NULL auto_increment,
foreign_id INT NOT NULL ,
view_date TIMESTAMP DEFAULT now(),
FOREIGN KEY (foreign_id) REFERENCES scrolls ( id ),
PRIMARY KEY(id)
);
CREATE TABLE IF NOT EXISTS sumarian_wheat_tablet_history
(
id INT NOT NULL auto_increment,
foreign_id INT NOT NULL ,
view_date TIMESTAMP DEFAULT now(),
FOREIGN KEY (foreign_id) REFERENCES sumarian_wheat_tablets ( id ),
PRIMARY KEY(id)
);
Is there a more correct way to handle such situations without making n new tables? I realize that I can make one history table and copy it over with CREATE TABLE...LIKE... but that still requires making n new tables, plus I have to go in and ALTER the foreign_id.
My first thought is just dump the fk reference and have one history table:
CREATE TABLE history(
base_table VARCHAR,
base_table_id INT,
view_date TIMESTAMP DEFAULT now()
);
But I assume you want the fk to maintain the integrity (question: is this really necessary, or can this be worked around?). I guess you could accomplish this by creating a table of "pks in use". For example:
create a table "keys" with columns id (autoincrement) and base_table_name
create a table "movies", where id is both pk and also a fk to "keys.id" (but not an autoincrement column)
add a "before insert" trigger to "movies" which inserts a record into "keys" returning the generated id to be used as the id for the "movie" record
create a history table with a fk to "keys"
create a "delete" trigger on "movies" which also removes the record from "keys" if you want the integrity maintained, or cascading deletes, etc
So the generated "id" is shared across many tables. There is a school of thought that suggests using a primary key unique across all relations within the database (an "enterprise key"), so it is not unprecedented. Instead of using sequences or autogenerated columns, sometimes a GUID or UUID is used.
This replaces extra history tables with triggers on each base table, which might not be a great thing, depending on your environment. I haven't done this myself, just throwing some thoughts out there, so take it for what its worth.
This depends on the record that you're keeping. If you just want to know hits, add one field to each table that is incremented each time your 'hit' criteria is met (ie, there is a read from a webpage). If you want to hold more information:
CREATE TABLE IF NOT EXISTS view_history
(
id INT NOT NULL,
table VARCHAR NOT NULL,
//other relevant stats to a given view, such as ip and so on.
)
The id and table form a composite key as to what table it refers to.
I don't think there is a way to specify more than one table on a single foreign key.
If you define a single history table, you cannot enforce referential integrity using a single foreign key. You could enforce it programmaticaly as explained here
This describes how to do it for other storage engines that do not support FKs, but could be used as a guide to implement what you need. It suggests creating triggers that will enforce same validations a foreign key would.
Other approach:
CREATE TABLE IF NOT EXISTS history
(
id INT NOT NULL auto_increment,
movie_id INT,
book_id INT,
song_id INT,
news_paper_id INT,
view_date TIMESTAMP DEFAULT now(),
FOREIGN KEY (movie_id) REFERENCES movie ( id ),
FOREIGN KEY (book_id) REFERENCES book ( id ),
FOREIGN KEY (song_id) REFERENCES song ( id ),
FOREIGN KEY (news_paper_id) REFERENCES news_paper ( id ),
PRIMARY KEY(id)
);

Self relationship in MySQL

I am trying to add a self relation in an existing Innodb table here is table structure
Table person
person_id int (10) primary key not null auto increment,
parent_id int (10) NULL default null,
name varchar(30)
When I use this command
ALTER TABLE `person` ADD FOREIGN KEY ( `parent_id` ) REFERENCES `person` (`person_id`) ON DELETE RESTRICT ON UPDATE RESTRICT ;
I get the error data type mismatch. I think this could be due to null values in parent_id. Is there any way to skip this check?
Thanks
person_id and parent_id need to be the exact same data type. For example, if person_id is INT UNSIGNED and parent_id is INT, then you can't create the foreign key.
Run this command and compare the data types of the two columns:
SHOW CREATE TABLE `person`\G