How to create tables to store different fields and reference them? - mysql

I have a set of users of different types, each type has individual set of fields storing user settings. My thought was to store user_id and user_type in one table with common set of fields and to move other settings to a separate tables. But the problem is how to link user from common table with his details in separate table. I see one solution is to store table name associated with certain user type in another table. But is it the best solution?
CREATE TABLE IF NOT EXISTS `mydb`.`user` (
`user_id` INT NOT NULL,
`user_name` INT NOT NULL,
`user_type` INT NULL,
PRIMARY KEY (`user_id`, `user_name`),
UNIQUE INDEX `adv_id_UNIQUE` (`user_id` ASC),
INDEX `adv_type_idx` (`user_type` ASC),
CONSTRAINT `adv_type`
FOREIGN KEY (`user_type`)
REFERENCES `mydb`.`user_type` (`type_id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
CREATE TABLE IF NOT EXISTS `mydb`.`user_type` (
`type_id` INT NOT NULL,
`type_table` VARCHAR(45) NULL,
UNIQUE INDEX `type_id_UNIQUE` (`type_id` ASC),
PRIMARY KEY (`type_id`))
//TABLES WITH SEPARATE SET OF FIELDS
CREATE TABLE IF NOT EXISTS `mydb`.`user_details_admin` (
`user_id` INT NOT NULL,
`user_admin` VARCHAR(45) NULL,
PRIMARY KEY (`user_id`))
CREATE TABLE IF NOT EXISTS `mydb`.`user_details_moderator` (
`user_id` INT NOT NULL,
`user_moderator` VARCHAR(45) NULL,
PRIMARY KEY (`user_id`))

This appears to be a situation where you want to model inheritance in your database.
Rather than storing the user_details_ table names in the user_types table, something akin to the following may serve you better:
CREATE TABLE IF NOT EXISTS 'mydb'.'user' (
'user_id' INT NOT NULL,
'type_id' INT NOT NULL,
'commonfield1' datatype (NOT) NULL,
'commonfield2' datatype (NOT) NULL,
'commonfield...' datatype (NOT) NULL,
PRIMARY KEY ('user_id', (other field as needed)),
UNIQUE INDEX 'adv_id_UNIQUE' ('user_id' ASC),
INDEX 'adv_type_idx' ('type_id' ASC),
CONSTRAINT 'adv_type'
FOREIGN KEY ('type_id')
REFERENCES 'mydb'.'user_type' ('type_id')
ON DELETE NO ACTION
ON UPDATE NO ACTION)
CREATE TABLE IF NOT EXISTS 'mydb'.'user_type' (
'type_id' INT NOT NULL,
'type_name' VARCHAR(45) NOT NULL,
UNIQUE INDEX 'type_id_UNIQUE' ('type_id' ASC),
UNIQUE INDEX 'type_name_UNIQUE' ('type_name' ASC),
PRIMARY KEY ('type_id'))
//TABLES WITH SEPARATE SET OF FIELDS
CREATE TABLE IF NOT EXISTS 'mydb'.'user_details_admin' (
'user_id' INT NOT NULL,
'type_id' INT NOT NULL,
'adminfield1' datatype (NOT) NULL,
'adminfield...' datatype (NOT) NULL,
PRIMARY KEY ('user_id'))
CONSTRAINT user_type_FK
FOREIGN KEY ('user_id', 'type_id')
REFERENCES 'mydb'.'user' ('user_id', 'type_id')
ON DELETE NO ACTION
ON UPDATE NO ACTION)
CREATE TABLE IF NOT EXISTS 'mydb'.'user_details_moderator' (
'user_id' INT NOT NULL,
'type_id' INT NOT NULL,
'moderatorfield1' datatype (NOT) NULL,
'moderatorfield...' datatype (NOT) NULL,
PRIMARY KEY ('user_id'))
CONSTRAINT user_type_FK
FOREIGN KEY ('user_id', 'type_id')
REFERENCES 'mydb'.'user' ('user_id', 'type_id')
ON DELETE NO ACTION
ON UPDATE NO ACTION)
This design assumes that a user may be of one and only one type. You'll need to insure that, for example, a moderator is only added to the user_details_moderator table using triggers or views, and/or by handling it in your application code. MySQL doesn't implement check constraints on tables. You'll likely want to create views, anyway, in order to avoid having to write the JOIN between the user table and the sub-type tables every time you want to query a specific sub-type.
Note: The INDEX on type_id in the user table may not be useful or necessary.
This is not the only way to model your data. If you have few fields that are distinct between types and/or are willing to have fields you know will be NULL in your table, you can just add all the fields to the user table. Other than the a priori NULL fields issue, a major difference between these approaches comes with the addition of a new user_type with new distinct fields. In the example I provided, you would need to add a new table. In the single-table design, you would need to add new nullable fields to the user table. Which is easier to maintain is really up to you, but I personally prefer the table-per-type design because in my uses adding a table is relatively trivial and I dislike intentionally adding fields that I know will contain NULL 'values' without serious optimization advantages (that don't exist in my case, but might in yours).
See also How do you effectively model inheritance in a database?, and/or search for "inheritance" under the database tag for further information.

I think creating a user_type_parameters with columns user_id, parameter_key, parameter_value could be an interesting solution as it would give you more flexibility.
the parameter_key column would be the name of some parameter like one of the columns on the user_details_admin table, and on the parameter_value column you would inster its correspondent value.
Of course on the application side you would have to know what keys to expect for each user type.
please fell free to ask if you have any doubts about my explanation.

Related

Could a foreign key column has multivalue in one row from the same reference table?

I'm trying to create a database on MySQL Workbench. Is it legal if a foreign key column has a multivalue in one row? I want to do that because based on the event category the user would be allowed to see the event or not. Also, there could be multiple event categories for one event. I know that I can make a composite key to event table. But I'm wondering that can I have multivalue as a foreign key in one row?
Here is my Event table:
CREATE TABLE IF NOT EXISTS `mydb`.`EVENT` (
`eventID` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`eventCategory` INT UNSIGNED NOT NULL,
`name` VARCHAR(45) NOT NULL,
`eventDescription` VARCHAR(280) NULL,
`date` DATETIME(1) NOT NULL,
`locationDescription` VARCHAR(45) NOT NULL,
`regionID` INT UNSIGNED NOT NULL,
PRIMARY KEY (`eventID`),
INDEX `fk_EVENT_category_1_idx` (`eventCategory` ASC) VISIBLE,
INDEX `fk_EVENT_region_1_idx` (`regionID` ASC) VISIBLE,
CONSTRAINT `fk_EVENT_region_1`
FOREIGN KEY (`regionID`)
REFERENCES `mydb`.`REGION` (`regionID`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_EVENT_category_1`
FOREIGN KEY (`eventCategory`)
REFERENCES `mydb`.`CATEGORY` (`categoryID`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
And I want to store multiple categories in one ro. I want to do that because based on those categories, some of the users wouldn't be allowed to see the event in the application.
Here is my category table:
CREATE TABLE IF NOT EXISTS `mydb`.`CATEGORY` (
`categoryID` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`disorderID` INT UNSIGNED NOT NULL,
`categoryDescription` VARCHAR(45) NOT NULL,
PRIMARY KEY (`categoryID`, `disorderID`),
INDEX `fk_CATEGORY_disorder_1_idx` (`disorderID` ASC) VISIBLE,
CONSTRAINT `fk_CATEGORY_disorder_1`
FOREIGN KEY (`disorderID`)
REFERENCES `mydb`.`DISORDERS` (`disorderID`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
And I want to store multiple categories in one ro. I want to do that because based on those categories, some of the users wouldn't be allowed to see the event in the application.
No. Don't got that way. This would denormalize your schema and make simple things utterly complex later on.
You have a many-to-many relationship between event and categories. The proper way to represent that is to create a third table, where each event/category table is stored on a separate row.
Something like:
create table event_categories (
event_id int not null,
category_id int not null,
primary key (event_id, category_id),
foreign key(event_id) references event(event_id),
foreign key(category_id) references category(category_id),
);

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.

Database design for an entity that might comprise from other same entities

I have some sport facilities that have fields that one can play 5x5 football in them. I am trying to make a simple reservation system for them.
My problem is that some fields combine and make bigger fields that the manages of the facilities want to treat them as their own entities (makes sense, if they book them like that why not).
Let's give an actual example.
We have facility FA. They have 3 5x5 fields one next to another, let's call then sa, sb, sc and any two of them can combine to make a 7x7 field, let's call it dd and all three to make a 10x10 field, let's call it te.
This happens with the other facilities as well but this is the more extreme case.
I have been trying to think how to model the tables for the fields when I make the reservation and deal with it but I am not sure.
One solution I have is to have a table for the fields
CREATE TABLE IF NOT EXISTS field (
id SMALLINT(5) UNSIGNED NOT NULL AUTO_INCREMENT,
arena_id SMALLINT(4) UNSIGNED NOT NULL,
internal_id TINYINT(3) UNSIGNED NOT NULL,
is_composite BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (id),
UNIQUE (arena_id, internal_id),
CONSTRAINT fk_field_arena_id FOREIGN KEY (arena_id) REFERENCES arena(id) ON UPDATE CASCADE ON DELETE CASCADE
)
;
And theh have a one to one or zero relationship with another table
CREATE TABLE IF NOT EXISTS field_component (
field_id SMALLINT(5) UNSIGNED NOT NULL,
component SMALLINT(5) UNSIGNED NOT NULL,
PRIMARY KEY (field_id, component),
CONSTRAINT fk_field_component_field_id FOREIGN KEY (field_id) REFERENCES field(id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT fk_field_component_field_id2 FOREIGN KEY (component) REFERENCES field(id) ON UPDATE CASCADE ON DELETE CASCADE
)
;
that will have entries with the fields that comprise a component field. An entry will exist here only when the flag is_composite in the field table is true.
A simpler solution I was thinking that is a bit more manual, was instead of having the second table and the flag, to just have a string column where I put the ids of the fields that make the composite field as a comma separated list.
On a separate note, I was thinking of moving the flag is composite to a third table called field_info that I might have one to one relationship with the field and will contain information about each field. i.e. size, material of the ground, if it's composite or not, notes about it etc.
Any thought suggestions, criticism, alternatives are welcome.
I would consider the following, that ensures in the composite table field_component that the child and composite arenas are at least the same. Note that InnoDB check constraints are not enforced.
Point #1: The is_composite quality in the following for the field_component implicitly pointing back to something that is truly a composite is not enforced. It could be with more compositing (meaning more tables).
Point #2: The datatypes should not be overly engineered into small and tiny INTs at this stage or perhaps ever. Especially if new to mysql.
Point #3: The FK relationships have the tendency to create KEYS for you automatically when not present in the child table. The unique key that we explicitly have in field_component effectly serves two purposes. It enforces non-dupes, and it serves as the index used where an FK auto-gen one would have been generated. Another one is generated automatically as can be seen in show create table. So, our UNIQUE KEY serves a few purposes there.
CREATE TABLE IF NOT EXISTS field (
id SMALLINT(5) UNSIGNED NOT NULL AUTO_INCREMENT,
arena_id SMALLINT(4) UNSIGNED NOT NULL,
internal_id TINYINT(3) UNSIGNED NOT NULL,
is_composite BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (id),
UNIQUE (arena_id, internal_id),
CONSTRAINT fk_field_arena_id FOREIGN KEY (arena_id) REFERENCES arena(id) ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS field_component (
field_id SMALLINT(5) UNSIGNED NOT NULL,
component SMALLINT(5) UNSIGNED NOT NULL,
PRIMARY KEY (field_id, component),
CONSTRAINT fk_field_component_field_id FOREIGN KEY (field_id) REFERENCES field(id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT fk_field_component_field_id2 FOREIGN KEY (component) REFERENCES field(id) ON UPDATE CASCADE ON DELETE CASCADE
);
TweakA:
CREATE SCHEMA TweakA;
USE TweakA;
-- drop table arena
CREATE TABLE IF NOT EXISTS arena
( id INT PRIMARY KEY,
aName varchar(200) NOT NULL
);
-- drop table field
CREATE TABLE IF NOT EXISTS field
( id INT AUTO_INCREMENT PRIMARY KEY,
arena_id INT NOT NULL, -- like the Arena #
internal_id INT NOT NULL, -- 1, 2, 3 for the field #
is_composite BOOLEAN NOT NULL DEFAULT FALSE,
friendly_name VARCHAR(100) NOT NULL,
UNIQUE KEY (arena_id, internal_id),
CONSTRAINT fk_field_arena_id FOREIGN KEY (arena_id) REFERENCES arena(id) ON UPDATE CASCADE ON DELETE CASCADE
);
-- drop table field_component
CREATE TABLE IF NOT EXISTS field_component
( id INT AUTO_INCREMENT PRIMARY KEY,
arena_id INT NOT NULL,
child_internal_id INT NOT NULL,
composite_internal_id INT NOT NULL,
-- The following UK will pick up part of what I will explain in the Narrative
UNIQUE KEY `unq_arena_comp_child` (arena_id,child_internal_id,composite_internal_id),
CONSTRAINT fk_field_child_field_id FOREIGN KEY (arena_id,child_internal_id)
REFERENCES field(arena_id, internal_id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT fk_field_composite_field_id FOREIGN KEY (arena_id,composite_internal_id)
REFERENCES field(arena_id, internal_id) ON UPDATE CASCADE ON DELETE CASCADE
-- note that InnoDB check constraints are not effective
);
-- Note, look at output from the following
-- show create table field_component; -- this shows the auto-gen of 1 key due to FK
--
-- The following block is a Helper block during testing
-- Truncate in reverse order:
-- TRUNCATE TABLE field_component;
-- TRUNCATE TABLE field;
-- TRUNCATE TABLE arena;
-- test data load:
INSERT arena(id,aName) VALUES (1,'Boston Arena, North Shore');
INSERT field(arena_id,internal_id,is_composite,friendly_name) VALUES
(1,1,FALSE,'sa'),
(1,2,FALSE,'sb'),
(1,3,FALSE,'sc'),
(1,4,TRUE,'dab'),
(1,5,TRUE,'dac'),
(1,6,TRUE,'dbc'),
(1,7,TRUE,'abc');
INSERT field_component(arena_id,child_internal_id,composite_internal_id) VALUES
(1,1,4),
(1,2,4),
(1,1,5),
(1,3,5),
(1,2,6),
(1,3,6),
(1,1,7),
(1,2,7),
(1,3,7); -- SUCCESS
INSERT field_component(arena_id,child_internal_id,composite_internal_id) VALUES
(2,2,4); -- will fail, as expected
INSERT field_component(arena_id,child_internal_id,composite_internal_id) VALUES
(1,72,4); -- will fail, as expected
INSERT field_component(arena_id,child_internal_id,composite_internal_id) VALUES
(1,1,444); -- will fail, as expected
show create table field_component;
-- the above will exhibit the AUTO_INCREMENT gap anomoly due to the above
-- expected failed inserts, setting AI=13 or so
DROP SCHEMA TweakA;
I wrote an FK Enforces Composite Relationship answer that got a little complicated. Yours can go that route depending on the level of DB Enforcement you are looking for.
Also see the MySQL Using FOREIGN KEY Constraints concerning auto-gen of KEYS due to FK relationships as mentioned in Point #3.
So, this answer could just keep growing as you work thru enforcement. Or do it client side. If it were me, I would do it DB Enforcement.
Regardless, as mentioned in comments, don't store CSV values in a column.

How do you create a constraint on parent tables that also constrains the child tables?

I am not sure how to phrase the question so I'll illustrate the tables and the explain what I want to achieve.
-- static table of the entity classes supported by the application
create table entity_type (
id integer not null auto_increment,
name varchar(30) not null,
primary key(id)
);
-- static table of statuses supported by the application
create table entity_status (
id integer not null auto_increment,
name varchar(30) not null,
primary key(id)
);
-- table of valid combinations
create table entity_type_entity_status_link (
entity_type_id integer not null,
entity_status_id integer not null,
unique key(entity_type_id, entity_status_id),
foreign key(entity_type_id) references entity_type(id),
foreign key(entity_status_id) references entity_status(id),
);
-- The tables where user types and statuses are defined
create table user_type (
id integer not null auto_increment,
name varchar(30) not null,
entity_type_id integer not null,
primary key(id),
foreign key(entity_type_id) references entity_type(id)
);
create table user_status (
id integer not null auto_increment,
name varchar(30) not null,
entity_status_id integer not null,
primary key(id),
foreign key(entity_status_id) references entity_status(id)
);
-- table of valid pairs
create table user_type_user_status_link (
user_type_id integer not null,
user_status_id integer not null,
unique key(user_type_id, user_status_id),
foreign key(user_type_id) references user_type(id),
foreign key(user_status_id) references user_status(id),
);
The basic premise behind these tables is that the system supports core types and statuses and the user is able to create their own user types and statues that derive from these.
The question I have is that I cannot see a way of creating any database constraints on the user_type_user_status_link table to ensure that the you cannot insert a file_type - file_status pair where the parent entity_type - entity_status is itself not valid. Or is this something that would have to be done with triggers.
The basic premise behind these tables is that the system supports core
types and statuses and the user is able to create their own user types
and statues that derive from these.
Although that sounds like a laudable goal on the surface, the effect is to delegate database design to your users. Database design, because the effect of your desire to set foreign key references to a subset of the rows in entity_type_entity_status_link means each of those subsets is a defacto, unnamed table.
This approach never ends well.
What you've developed is the "One True Lookup Table". Google that for a host of reasons why OTLT is an anti-pattern.
The best solution is to model real things in your tables. (Entity isn't a real thing. It's an abstraction of a real thing.) Something along the lines of either
create table file_status (
file_status varchar(30) primary key
);
or
create table file_status (
file_status_id integer primary key,
file_status varchar(30) not null unique
);
would work well for file statuses.
In the case of the second one, you can set a foreign key reference to either the id number (saves space, requires an additional join) or to the status text (takes more space, eliminates a join). Note that you need the unique constraint on the status text; your original design allows the user to enter the same text multiple times. (You could end up with 30 rows where entity_type.name is 'File'.
You should use triggers for that.
MySQL does not support constraints of the form that will prevent what you want.

Primary and foreign key in SQL?

I am not sure about this , but do I need to create foreign key explicitly in the SQL command?
This guy did this:
CREATE TABLE languages (
lang_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT,
lang VARCHAR(60) NOT NULL,
lang_eng VARCHAR(20) NOT NULL,
PRIMARY KEY (lang_id),
UNIQUE (lang)
);
CREATE TABLE threads (
thread_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
lang_id TINYINT(3) UNSIGNED NOT NULL,
user_id INT UNSIGNED NOT NULL,
subject VARCHAR(150) NOT NULL,
PRIMARY KEY (thread_id),
INDEX (lang_id),
INDEX (user_id)
);
In this case, does it mean that INDEX(lang_id) becomes FOREIGN KEY automatically? I know INDEX makes search go faster, but I don't understand the part about foreign key
I would really appreciate any answer
No. An index is just that... an index on a field. A foreign key tells MySQL that "this particular field MUST have a matching record in that table over there".
MySQL's internal design requires that all fields used as foreign keys be indexed, but modern versions will automatically create that index for you.
The converse is not true, whoever. Adding an index to a field does not turn it into a foreign key - a foreign key definition must also include what the foreign table/field is, and a simple index declaration has none of that information.
For your sample table, you'd need to have
...
INDEX (lang_id),
FOREIGN KEY (lang_id) REFERENCES languages (lang_id),
...
to produce a foreign key.
A foreign key means that the value(s) must exist in the referenced column(s). It is not automatic - you need to write it explicitly.
FOREIGN KEY lang_id REFERENCES languages (lang_id)
No a foreign key has to be explicitly declared
CREATE TABLE threads (
thread_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
lang_id TINYINT(3) UNSIGNED NOT NULL FOREIGN KEY FK_1 REFERENCES languages(lang_id),
user_id INT UNSIGNED NOT NULL,
subject VARCHAR(150) NOT NULL,
PRIMARY KEY (thread_id),
INDEX (lang_id),
INDEX (user_id)
);
What you now have are two tables with primary keys and indexes on those primary key values.
You could stop here if you want but you won't have declared referential integrity enforcing that relationship between laguage and threads tables.
To do that you would explicitly create a foreign key relationship as explained here - http://dev.mysql.com/doc/refman/5.5/en/innodb-foreign-key-constraints.html
The differences are as follows :-
The primary key identifies a record uniquely in a table with multiple rows.
An index is a generic term, where by you can create more than one index for a table, in this case the database creates indexes based on the columns that you specified, so that when you query the appropriate index will kick in and give you results faster.
A foreign key on the other hand says that this column in table b, is the primary column in table A, so that whenever you enter rows into table B the databse will check that the specified column/data exists in table A otherwise it will throw an error.