I am working to build an inheritance database model within MySQL, such that all tables inherit from one base type (object), represented by the object table. This allows for Notes to be linked to any object from any table within the database while retaining referential integrity. The design looks something like this (there are a lot more child tables with similar structures):
CREATE TABLE object
(
object_id INT(10) AUTO_INCREMENT,
object_type VARCHAR(80),
PRIMARY KEY (object_id)
);
CREATE TABLE person
(
person_id INT(10),
name_first VARCHAR(80),
name_last VARCHAR(80),
email_address VARCHAR(80),
PRIMARY KEY (person_id),
CONSTRAINT fk_person FOREIGN KEY (person_id)
REFERENCES object (object_id)
);
CREATE TABLE note
(
note_id INT(10),
not_text TEXT,
note_subject_id INT(10),
PRIMARY KEY (note_id),
CONSTRAINT fk_note FOREIGN KEY (note_id)
REFERENCES object (object_id),
CONSTRAINT fk_note_subject FOREIGN KEY (note_subject_id)
REFERENCES object (object_id)
);
With this design, I am able to make a note with a person as the subject, a note with another note as the subject, or a note with one of the many other tables inheriting from object as a subject (these tables are not listed for brevity). Although it cannot be enforced through referential integrity, a presupposition of this design is that each object_id is used in only one row of one child table, so that there are no notes where the note_id is also a person_id.
The problem occurs when I want to perform INSERT... SELECT statement on person or note. Let's say that I have a user table and I would like to insert all users in to person. First I have to insert the number of new person rows I am creating into object, then I need to insert the new rows into person, but I have no way of matching each user row to an object row in order to populate the person_id column.
My first thought was to create a BEFORE INSERT TRIGGER on person that would create the new object record and update the NEW.person_id value accordingly. Unfortunately, the foreign key constraint is being evaluated before the trigger is allowed to fire, catching my orphaned row before I can correct it.
What I am looking for is either a way to change the order of constraint execution so that BEFORE INSERT triggers precede constraints, or for a more elegant way to achieve an Class Table Inheritance database structure within MySQL.
This is not object orientation, you just want to implement inheritance in your RDBMS. You have three choices: Horizontal Mapping, Vertical Mapping, Filtered Mapping.
Reference: http://modeling.sourceforge.net/UserGuide/design-inheritance.html
You may not need inheritance though, if you relax a little on reference integrity. Your Note table may contain multiple nullable foreign keys, one for each table you want to add the note to.
Related
I have a database of polymorphic structure: a "base" type table and two "derived" types:
CREATE TABLE ContactMethod(
id integer PRIMARY KEY
person_id integer
priority integer
allow_solicitation boolean
FOREIGN KEY(person_id) REFERENCES People(id)
)
CREATE TABLE PhoneNumbers(
contact_method_id integer PRIMARY KEY
phone_number varchar
FOREIGN KEY(contact_method_id) REFERENCES ContactMethod(id)
)
CREATE TABLE EmailAddresses(
contact_method_id integer PRIMARY KEY
email_address varchar
FOREIGN KEY(contact_method_id) REFERENCES ContactMethod(id)
)
I want to prevent orphaned ContactMethod records from existing, that is, a ContactMethod record with neither a corresponding PhoneNumber record nor an EmailAddress record. I've seen techniques for ensuring exclusivity (preventing a ContactMethod record with both a related PhoneNumber and EmailAddress), but not for preventing orphans.
One idea is a CHECK constraint that executes a custom function that executes queries. However, executing queries via functions in CHECK constraints is a bad idea.
Another idea is a View that will trigger a violation if an orphaned ContactMethod record is added. The "obvious" way to do this is to put a constraint on the View, but that's not allowed. So it has to be some sort of trick, probably involving an index on the View. Is that really the best (only?) way to enforce no orphans? If so, what is a working example?
Are there other ways? I could get rid of ContactMethod table and duplicate shared columns on the other two tables, but I don't want to do that. I'm primarily curious about capabilities available in MySQL and SQLite, but a solution in any SQL engine would be helpful.
The simplest solution would be to use single table inheritance. So both the contact methods are optional (that is, nullable) fields in the ContactMethod table, but you add a CHECK constraint to ensure at least one of these has a non-null value.
CREATE TABLE ContactMethod(
id integer PRIMARY KEY
person_id integer
priority integer
allow_solicitation boolean,
phone_number varchar DEFAULT NULL
email_address varchar DEFAULT NULL
FOREIGN KEY(person_id) REFERENCES People(id)
CHECK (COALESCE(phone_number, email_address) IS NOT NULL)
)
Another solution that supports polymorphic associations is to reverse the direction of foreign key. Make ContactMethod have a one nullable foreign key for each type of associated method. Use a CHECK to make sure at least one has a non-null value. This works because you don't allow multiple emails or phones per row in ContactMethod. It does mean if you add a different type of contact (e.g. Signal account), then you'd have to add another foreign key to this table.
CREATE TABLE ContactMethod(
id integer PRIMARY KEY
person_id integer
priority integer
allow_solicitation boolean,
phone_number_id integer DEFAULT NULL
email_address_id integer DEFAULT NULL
FOREIGN KEY(person_id) REFERENCES People(id)
FOREIGN KEY(phone_number_id) REFERENCES PhoneNumbers(id)
FOREIGN KEY(email_address_id) REFERENCES EmailAddresses(id)
CHECK (COALESCE(phone_number_id, email_address_id) IS NOT NULL)
)
A newly inserted ContactMethod will always be orphaned until you insert a phone number or an e-mail address. So, you cannot test the condition at insert.
Instead, you could insert contact information with a stored procedure having an optional phone number and optional e-mail parameter in addition to the base information. The base record would only be inserted if at least one of the two has a non-null value.
Then create a delete trigger when a phone number or an e-mail address is deleted, to either delete the ContactMethod record when no related record exist anymore or to raise an exception as shown in Alter a Delete Trigger to Check a Column Value
I want to have an "Entity" and many versions of it, where one of those versions is the only one which is active/used. It is also possible that the Entity is entirely deactive. So I thought of using two tables with cyclic foreign keys like this:
CREATE TABLE entity (
id int NOT NULL AUTO_INCREMENT,
-- some extra irrelevant data commented out
active_version_id int DEFAULT NULL,
PRIMARY KEY (id)
);
CREATE TABLE entityversion (
id int NOT NULL AUTO_INCREMENT,
-- some extra irrelevant data commented out
entity_id int NOT NULL,
PRIMARY KEY (id)
);
ALTER TABLE entity ADD FOREIGN KEY (active_version_id) REFERENCES entityversion(id) ON DELETE SET NULL;
ALTER TABLE entityversion ADD FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE;
I would like to, when creating a new active Entity, to create at the same time its first EntityVersion which will be its active_version. The problem is we don't have their ids yet. Currently, we're creating the Entity with "returning id" and using that to create the EntityVersion, also with "returning id", and then updating the active_version_id of that same Entity, so 3 separate commands like this for example:
INSERT INTO entity DEFAULT VALUES RETURNING id;
-- get the ID back and use it as a parameter to the next command
INSERT INTO entityversion (entity_id) VALUES (%s) RETURNING id;
-- again the same thing
UPDATE entity SET active_version_id = %s WHERE id = %s;
I would like to know if there is a shorter way to do this. I also accept as answer a different approach to the table schemas, if it happens to be the better choice. Thanks for the help!
Create both your rows in a stored procedure, or use a before insert trigger if there is no data that only goes in the entityversion version table. To deal with your cyclical id problem, in mariadb use a sequence instead of auto_increment. In mysql, emulate a sequence with an entity_sequence table that only contains the auto_increment id. In your stored procedure/trigger, get the sequence value (with insert..returning id if emulating a sequence), store entityversion using that value, then set the entityversion id to store in your entity row.
You are implying that the entities are 1:1, in which case they may as well be in the same table. (Make one of the NULLable if it is not to inserted until later.)
If it is 1:many (a 'latest' and many 'older' versions), then the FK only goes one way.
In either case, your "circular" FKs go away.
But to answer your question:
Turn off FK checks
CREATE both tables
Populate both tables
ALTER to add both FKs
Turn on FK checks.
More
Well, it seems that you have many:1, not 1:1. The "History" has a column that is the "id" into the "Current" ('active') table. No circular FKs. Index that column so you can go the other way efficiently. ON DELETE CASCADE is not practical in either direction.
The FK should go one direction, not both.
I have two (mysql) tables -- company and user. They are structured as follows:
`user`
- id
- company_id (FK to company)
- name
`company`
- id
- name
- admin_user_id (FK to user)
The user table foreign keys to the company table, and vice versa.
I was wondering if the above pattern is ok, and if it's not why it's not, what could be done to improve it.
At a high level, your data model makes sense. However, you have no guarantee that admin_user_id points to a user in the same company. You can solve this by doing:
create table users (
user_id int auto_increment primary key,
company_id int,
name varchar(255),
unique (company_id, user_id) -- redundant but desirable for the foreign key reference
);
create table companies (
company_id int auto_increment primary key,
name varchar(255),
admin_user_id int,
foreign key (company_id, admin_user_id) references users(company_id, user_id)
);
alter table users
add constraint fk_users_company_id
foreign key (company_id) references companies (company_id);
If it is an accurate representation of your business domain then that's what really matters. There is a possible problem in SQL however. SQL only allows one table to be updated at once so either company or user has to come first. Normally you have to allow the constraint to be broken temporarily, either when you insert a new company with no corresponding user, or insert a new user with no corresponding company. To achieve that you can make one of the foreign keys nullable or you can temporarily disable the constraint.
Some data modellers dislike circular dependencies, even in purely conceptual models where the limitations of SQL are irrelevant. A few people will perceive circular dependencies as a modelling mistake - wrongly in my opinion. Disapproval of circular dependencies seems to be associated with ER modelling specifically. Interestingly, circular self-referential dependencies are taken for granted in Object Role Modelling which even has a special notation for them (ring constraints).
I am trying to set up a bunch of foreign key's in MySQL Workbench. It all seems to be working fine except that when I reverse engineer the EER diagram the relationship is always coming up as one to many rather than one to one. For my purposes I need to establish both types in different tables and I was wandering what I can do to control the type of relationship when I set up the foreign key.
Sorry if I am completely missing something obvious / basic but I am a beginner at this. Below I have included a screen shot of my current set-up of a foreign key that I want to result in a one to one relationship but is currently giving me a one to many. All help is greatly appreciated.
A 1:1 relationhip can be defined as follows
CREATE TABLE Table1
(
ID INT PRIMARY KEY,
Name VARCHAR(255)
);
CREATE TABLE Table2
(
ID INT PRIMARY KEY,
OtherDetails VARCHAR(255),
FOREIGN KEY (ID) REFERENCES Table1(ID)
);
To insert a record into Table2, the ID value must be present in Table1 (the Table2 foreign key constraint enforces this) and can only be added to Table2 once (Primary key constraint enforces this)
I have following comments table in my app:
comments
--------
id INT
foreign_id INT
model TEXT
comment_text TEXT
...
the idea of this table is to store comments for various parts of my app - it can store comments for blog post i.e.:
1|34|blogpost|lorem ipsum...
user picture:
2|12|picture|lorem ipsum...
and so on.
now, is there a way to force FOREIGN KEY constraint on such data?
i.e. something like this in comments table:
FOREIGN KEY (`foreign_id`) REFERENCES blogposts (`id`)
-- but only when model='blogpost'
You're attempting to do a design that is called Polymorphic Associations. That is, the foreign key may reference rows in any of several related tables.
But a foreign key constraint must reference exactly one table. You can't declare a foreign key that references different tables depending on the value in another column of your Comments table. This would violate several rules of relational database design.
A better solution is to make a sort of "supertable" that is referenced by the comments.
CREATE TABLE Commentable (
id SERIAL PRIMARY KEY
);
CREATE TABLE Comments (
comment_id SERIAL PRIMARY KEY,
foreign_id INT NOT NULL,
...
FOREIGN KEY (foreign_id) REFERENCES Commentable(id)
);
Each of your content types would be considered a subtype of this supertable. This is analogous to the object-oriented concept of an interface.
CREATE TABLE BlogPosts (
blogpost_id INT PRIMARY KEY, -- notice this is not auto-generated
...
FOREIGN KEY (blogpost_id) REFERENCES Commentable(id)
);
CREATE TABLE UserPictures (
userpicture_id INT PRIMARY KEY, -- notice this is not auto-generated
...
FOREIGN KEY (userpicture_id) REFERENCES Commentable(id)
);
Before you can insert a row into BlogPosts or UserPictures, you must insert a new row to Commentable to generate a new pseudokey id. Then you can use that generated id as you insert the content to the respective subtype table.
Once you do all that, you can rely on referential integrity constraints.
In MySQL 5.7 you can have a single polymorphic table AND enjoy something like a polymorphic foreign key!
The caveat is that technically you will need to implement it as multiple FKs on multiple columns (one per each entity that has comments), but the implementation can be limited to the DB side (i.e. you will not need to worry about these columns in your code).
The idea is to use MySQL's Generated Columns:
CREATE TABLE comments (
id INT NOT NULL AUTO_INCREMENT,
foreign_id INT,
model TEXT,
commented_text TEXT,
generated_blogpost_id INT AS (IF(model = 'blogpost', foreign_id, NULL)) STORED,
generated_picture_id INT AS (IF(model = 'picture', foreign_id, NULL)) STORED,
PRIMARY KEY (id) ,
FOREIGN KEY (`generated_blogpost_id`) REFERENCES blogpost(id) ON DELETE CASCADE,
FOREIGN KEY (`generated_picture_id`) REFERENCES picture(id) ON DELETE CASCADE
)
You can ignore the generated_* columns; they will be populated automatically by MySQL as comments are added or modified, and the FKs defined for them will ensure data consistency as expected.
Obviously it would impact both the size requirements and performance, but for some (most?) systems it would be negligible, and a price worth paying for achieving data consistency with a simpler design.