Jeremy Todd provided a good answer (+1 to him), I'd just like to make an additional point...
Is the "current" the same thing as "latest"? If yes, then you can use an identifying relationship and the resulting composite key to naturally model that:
All the revisions of the same page have the same PageRevision.PageId
, and their historical order within the page is determined by the integer RevNo
. The latest revision is simply the one with the highest RevNo
within its respective page.
Since InnoDB tables are clustered, this structure will group the revisions of the same page physically close together. Retrieving all the revisions of the page will be potentially much quicker than in your original structure, and retrieving only the latest revision will be about as quick.
Data modification will also be quicker, since we have one less index.
I could drop Page.CurrentRevisions and add PageRevisions.isCurrent, but I don't like that this design would allow more than one revision for a Page to be marked as current
Not that I'd recommend this approach, but the uniqueness of the "current" flag can be enforced declaratively, simply by using NULL instead of false:
CREATE TABLE PageRevision (
RevId INT PRIMARY KEY,
PageId INT NOT NULL,
IsCurrent BIT CHECK (IsCurrent IS NULL OR (IsCurrent IS NOT NULL AND IsCurrent = 1)),
UNIQUE (PageId, IsCurrent)
);
-- You can insert several "non current" revisions for the same page.
INSERT INTO PageRevision VALUES (1, 1, NULL);
INSERT INTO PageRevision VALUES (2, 1, NULL);
INSERT INTO PageRevision VALUES (3, 1, NULL);
-- You can insert one "current" revision in one page.
INSERT INTO PageRevision VALUES (4, 1, 1);
-- Or another "current" revision in a different page.
INSERT INTO PageRevision VALUES (5, 2, 1);
-- But not the second "current" revision in the same page.
-- The following violates the UNIQUE constraint:
INSERT INTO PageRevision VALUES (6, 1, 1);
NOTE: MySQL parses but doesn't enforce the CHECK constraint above. As a consequence you could have one (unwanted) false flag per page in addition to one (useful) true flag per page.
NOTE 2: Because of the peculiar nature of NULL, the CHECK above could be rewritten simply as: CHECK (IsCurrent = 1)
. When the flag is 0, the expression is false and the CHECK fails as expected. If the flag is 1 the expression is true and the CHECK passes. If the flag is NULL, the expression is NULL, and the CHECK passes (unlike WHERE which treats NULL as false). But I prefer to be a bit more explicit than that when dealing with NULLs.