Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I check for referential integrity in a given row after the row has been added?

I'm trying to see if there is a way to check the referential integrity of a single row after it has been inserted into an InnoDB table while foreign_key_checks is set to 0.

So given two tables:

CREATE TABLE `book` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `author_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `book_cc846901` (`author_id`),
  CONSTRAINT `author_id_refs_id_7fdd0933` FOREIGN KEY (`author_id`) REFERENCES `person` (`id`)
) ENGINE=InnoDB;


CREATE TABLE `person` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

We run the following statements against them:

SET foreign_key_checks=0
INSERT INTO `book` (`id`, `name`, `author_id`) VALUES (1, 'Cryptonomicon', 3)
INSERT INTO `person` (`id`, `name`) VALUES (4, 'Neal Stephenson')
SET foreign_key_checks=1

So we've put some bad data in here -- author_id refers to nonexistent person.id 3. I'm attempting to find a way to trigger a check of some kind on that row which will throw an error if there is a reference problem like this one.

The approach I initially was trying to take was to update the row with the exact same data. I tried this:

UPDATE `book` SET `name` = 'Cryptonomicon', `author_id` = 3 WHERE `book`.`id` = 1 

I expected this would trigger an error -- however, it did not. Since the data did not change, apparently no integrity check is made? Setting author_id to 2 does fail, for example.

My question then: Is there an alternative that would allow me to inspect a given row and trigger an integrity error if there is a problem? I've googled around but I have found nothing. I'd like to avoid checking the whole table but that might be the only route.

Any insights or other approaches that are escaping me would be appreciated.

(If you're curious why I'm trying to do all this, it's because I'm trying to help resolve an open issue in the Django framework where fixtures with forward references cannot be loaded. The proposed solution is to disable foreign key checks when fixtures are loaded and re-enabled them after the load is complete. I'm trying to figure out how at that point to go back and check the referential integrity of the rows that were added while foreign key checks were off and to raise an error if there is a problem.)

like image 778
jsdalton Avatar asked Jan 19 '23 09:01

jsdalton


1 Answers

I came up with a solution, which I actually got to thanks to the answer in this question: Force InnoDB to recheck foreign keys on a table/tables?

I had seen that question before, but I finally spent about 20 minutes unpacking all the abstractions and trying to understand what it is doing in concrete terms. It's really not all that complicated, actually. Phrasing the core of the solution in terms of the example I gave in this question, you've basically got two SELECT statements:

SELECT *
FROM information_schema.KEY_COLUMN_USAGE
WHERE
    `CONSTRAINT_SCHEMA` LIKE `test`
    AND `TABLE_NAME` = `book`

That will return all of the key columns in the book table. We can iterate over these results and check their references as follows -- in this example using "author_id":

SELECT * FROM `book` as REFERRING
LEFT JOIN `person` as REFERRED
ON (REFERRING.`author_id` = REFERRED.`id`)
WHERE REFERRING.`author_id` IS NOT NULL
AND REFERRED.`id` IS NULL

Basically, that statement will return any rows where there is a value in author_id but no corresponding value for id in the related table. These are the "bad rows".

I didn't need to trigger the error directly in MySQL -- for my purposes I can raise an error in application code -- so this was as far as I needed to go. But this ended up being a fairly straightforward and fast solution, so I'm glad I found it!

like image 84
jsdalton Avatar answered Jan 30 '23 01:01

jsdalton