Recording the relationship is easy enough - you could make a table foo_whatever (foo_id PK, whatever_set_id FK)
and insert rows only for appropriate foo ids. However, that schema doesn't enforce any constraint on the subtypes you can associate with whatever sets, but neither does your existing schema enforce that subtypes are mutually exclusive. It's possible to enforce both with the same technique.
Consider including a type indicator on all the foo_*
tables, e.g. using an enum('bar', 'baz', 'buz')
. This provides subtype information in foo
(which can be more convenient than joining 3 tables to find a match) and allows foreign key constraints and check constraints to enforce exclusive subtypes and restrict the types that can be recorded in foo_whatever
. Yes, it involves a bit of redundant information, but it's small and there's no risk of update anomalies.
Using composite foreign key constraints that involve a type indicator, together with check constraints that limit the value of the type indicator for each subtype table, should do the trick. Here's the schema I suggest:
CREATE TABLE `foo` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` enum('bar','baz','buz') NOT NULL,
PRIMARY KEY (`id`),
KEY `foo_id` (`id`,`type`)
);
CREATE TABLE `foo_type_bar` (
`foo_id` int(11) NOT NULL,
`foo_type` enum('bar','baz','buz') NOT NULL CHECK (foo_type = 'bar'),
PRIMARY KEY (`foo_id`),
KEY `foo_bar_fk` (`foo_id`,`foo_type`),
CONSTRAINT `foo_bar_fk` FOREIGN KEY (`foo_id`, `foo_type`)
REFERENCES `foo` (`id`, `type`) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE `foo_type_baz` (
`foo_id` int(11) NOT NULL,
`foo_type` enum('bar','baz','buz') NOT NULL CHECK (foo_type = 'baz'),
PRIMARY KEY (`foo_id`),
KEY `foo_baz_fk` (`foo_id`,`foo_type`),
CONSTRAINT `foo_baz_fk` FOREIGN KEY (`foo_id`, `foo_type`)
REFERENCES `foo` (`id`, `type`) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE `foo_type_buz` (
`foo_id` int(11) NOT NULL,
`foo_type` enum('bar','baz','buz') NOT NULL CHECK (foo_type = 'buz'),
PRIMARY KEY (`foo_id`),
KEY `foo_buz_fk` (`foo_id`,`foo_type`),
CONSTRAINT `foo_buz_fk` FOREIGN KEY (`foo_id`, `foo_type`)
REFERENCES `foo` (`id`, `type`) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE `foo_whatever` (
`foo_id` int(11) NOT NULL,
`foo_type` enum('bar','baz','buz') NOT NULL CHECK (foo_type IN ('baz', 'buz')),
`whatever_set_id` int(11) NOT NULL,
PRIMARY KEY (`foo_id`),
KEY `whatever_foo_fk` (`foo_id`,`foo_type`),
KEY `whatever_set_fk` (`whatever_set_id`),
CONSTRAINT `whatever_foo_fk` FOREIGN KEY (`foo_id`, `foo_type`)
REFERENCES `foo` (`id`, `type`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `whatever_set_fk` FOREIGN KEY (`whatever_set_id`)
REFERENCES `whatever_set` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
);
However, since MySQL ignores check constraints, you would need to use triggers to achieve the same:
DELIMITER ;;
CREATE TRIGGER foo_bar_insert_type_check
BEFORE INSERT ON foo_type_bar
FOR EACH ROW
BEGIN
IF NEW.foo_type != 'bar' THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'Invalid foo_type in foo_type_bar';
END IF;
END;;
CREATE TRIGGER foo_bar_update_type_check
BEFORE UPDATE ON foo_type_bar
FOR EACH ROW
BEGIN
IF NEW.foo_type != 'bar' THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'Invalid foo_type in foo_type_bar';
END IF;
END;;
CREATE TRIGGER foo_baz_insert_type_check
BEFORE INSERT ON foo_type_baz
FOR EACH ROW
BEGIN
IF NEW.foo_type != 'baz' THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'Invalid foo_type in foo_type_baz';
END IF;
END;;
CREATE TRIGGER foo_baz_update_type_check
BEFORE UPDATE ON foo_type_baz
FOR EACH ROW
BEGIN
IF NEW.foo_type != 'baz' THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'Invalid foo_type in foo_type_baz';
END IF;
END;;
CREATE TRIGGER foo_buz_insert_type_check
BEFORE INSERT ON foo_type_buz
FOR EACH ROW
BEGIN
IF NEW.foo_type != 'buz' THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'Invalid foo_type in foo_type_buz';
END IF;
END;;
CREATE TRIGGER foo_buz_update_type_check
BEFORE UPDATE ON foo_type_buz
FOR EACH ROW
BEGIN
IF NEW.foo_type != 'buz' THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'Invalid foo_type in foo_type_buz';
END IF;
END;;
CREATE TRIGGER foo_whatever_insert_type_check
BEFORE INSERT ON foo_whatever
FOR EACH ROW
BEGIN
IF NEW.foo_type NOT IN ('baz', 'buz') THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'Invalid foo_type in foo_whatever';
END IF;
END;;
CREATE TRIGGER foo_whatever_update_type_check
BEFORE UPDATE ON foo_whatever
FOR EACH ROW
BEGIN
IF NEW.foo_type NOT IN ('baz', 'buz') THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'Invalid foo_type in foo_whatever';
END IF;
END;;
DELIMITER ;