MySQL create both entities with cyclic foreign key - mysql

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.

Related

How to prevent orphaned polymorphic records?

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

"Segemented" foreign keys in MySQL?

I have a problem with foreign keys in MySQL or probably I am just thinking in the wrong direction... I have an activity log table where I need to reference key values from currently 2 other tables. So I am using a field that contains that foreign key value along with an indicator stating which table that foreign key value is from.
Table activitylog
...
RefID INT NOT NULL,
RefType INT NOT NUL,
...
Table offers
OfferID INT NOT NULL,
...
Table orders
OrderID INT NOT NULL,
...
If the user created an offer, the value of OfferID from table Offers would be writtten to RefID of activity log and RefType is set to 1. If it was an order then the value of OrderID goes into RefID and RefType is set to 2.
Of course I could add an additional field, name it OrderID, rename RefID to OfferID and discard RefType and use these fields. But if in future an new entity will be used I would have to add an additional field holding the key values of the new entity instead of just invent RefType 3 and continue having the key values in RefID.
I am now struggling with the definition of the foreing key constraints. The logic would be if RefType = 1 lookup the key in Offers, if RefType = 2 go into Orders.
Does anybody know if there is a way to achieve my current concept or do I have to add additional fields to the activitylog?
No. MySQL doesn't support enforcement of FOREIGN KEY constraints like you explain, a single column referencing multiple tables.
You could define the constraints with the MyISAM engine, but the FK constraints wouldn't be enforced.
If you define the FK constraints for tables using the InnoDB engine, then ALL of the foreign key constraints would be enforced, no matter what values are stored in other columns.
To have FK constraints on a table to reference two (or more) different, independent parent tables, you'd need two (or more) foreign keys columns, one for each table.
With your table design with InnoDB, you'd have to forgo declarative FOREIGN KEY constraints.
It might be possible for you to roll-your-own constraints by writing some messy triggers; have the trigger throw an exception when one of your constraint rules is violated.

Specify constraints in a MySQL table

I am in the process of designing the databases for my system. There are a lot of foreign key constraints.
I was wondering whether I could get some advice, whether I should do which of the following:
1) Specify the constraints during table creation itself ie,
CREATE TABLE IF NOT EXISTS abc
(
keyword VARCHAR(20) NOT NULL,
id INT UNSIGNED NOT NULL,
FOREIGN KEY (id) REFERENCES xyz(id) ON DELETE CASCADE ON UPDATE CASCADE
)ENGINE=InnoDB;
2)create the table without FK constraints and 'alter' the table later on ie,
CREATE TABLE IF NOT EXISTS abc
(
keyword VARCHAR(20) NOT NULL,
id INT UNSIGNED NOT NULL,
)ENGINE=InnoDB;
ALTER TABLE abc ADD CONSTRAINT fk_constraint FOREIGN KEY (id) REFERENCES xyz(id)
ON DELETE CASCADE ON UPDATE CASCADE;
Table xyz is simply another table with 'id' as a primary key.
You may create the FK at once. But this is not always possible because they can refer to each other in a circular fashion. Also, you may want to add columns later, with a FK.
It may be slightly faster to add it at once, because MySQL has to validate and rebuild the table structure for some changes (although I'm not sure adding FKs is one of those). But this process will be reasonably fast on empty tables, so it doesn't matter much when you add the FK.
The result will be the same. So, there is no differences.
If I create new database, I'd create table and its foreign key in one statement. The script will look better. But in this case parent tables must be created before the child tables.
If you don't want to take into account dependencies when creating tables, you can create tables in random order in the beginning of the script and then add foreign keys using ALTER TABLE.

Class Table Inheritance (CTI) and Insert Select statements

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.

MySQL Phone table: How to specify a unique primary contact number per ID?

My table is as follows:
CREATE TABLE IF NOT EXISTS PHONES (
number VARCHAR(10),
id INT,
type VARCHAR(10),
PRIMARY KEY (number),
FOREIGN KEY (id)
REFERENCES TECHNICIANS(id)
ON DELETE CASCADE
) ENGINE = INNODB;
I would like to specify for each id one primary contact number. I was thinking of adding a boolean column, but I can't figure out how to get it to only allow one "true" value per. id.
I'd add a foreign key from TECHNICIANS back to PHONES:
ALTER TABLE TECHNICIANS
ADD COLUMN primary_number VARCHAR(10),
ADD CONSTRAINT FOREIGN KEY (primary_number) REFERENCES PHONES (number)
ON UPDATE CASCADE
ON DELETE SET NULL;
This creates a cyclical reference: technicians references phones, and phones references technicians. This is okay, but it requires special handling when you do things like dropping tables, restoring backups, etc.
You've basically got 3 options...
have a boolean column but it's up to your application to maintain it
have an integer so you store priority (0=prime, 1=secondary, 2=tertiary,...) again you'll have to maintain it
Have a parent-child relationship so a parent (technician?) record has multiple child (phone number) records. The parent record would then also contain the Id of the primary child record. The only down-side is that adding records either becomes multi-step (add technician, add phone numbers, set primary phone number for technician) or you'll need a smart DAL which does it for you :)
Incidentally, I'm assuming you actually mean one primary number per TechnicianId not per PhoneId
Use a "loophole" in MySQL. The MySQL documentation says:
A UNIQUE index creates a constraint
such that all values in the index must
be distinct. An error occurs if you
try to add a new row with a key value
that matches an existing row. This
constraint does not apply to NULL
values except for the BDB storage
engine. For other engines, a UNIQUE
index permits multiple NULL values for
columns that can contain NULL.
This means that you can create a boolean column that has two values: true (or 1) and NULL.
Create a UNIQUE index over that column + your key. That allows you to only set one record to true, but any number of them can have NULL.