How can I resolve a three-way polymorphic associat

2019-04-08 20:46发布

问题:

First let me say I am using MySQL (not transactional) and that this can't be changed. Also, I have simplified the tables here for brevity and clarity.

In this example a 'Lesson' is comprised of it's internal attributes and an external attributes with it's own attributes 'Readings'. 'Readings' has it's own key dependent attributes and three distinct external attributes (the reading sources).

I want to avoid the polymorphic association that arrises here but am unable to wrap my head around it. In this example 'sourceId' in table 'Reading' would contain the id from one of three tables "ExternalURL", "InternalURL" and "Book". Also, the field "polytable" would contain the table name that the aforementioned "id" came from.

Can someone take a moment to please explain how to resolve this maintaining RI or is it possible that in the interest of efficiency that it should be left?

Thank you for your time and consideration,

Tim

                       -------------
                      | ExternalURL |
                       -------------
                      | id          |
                      | badlink     |
                      | url         |
                      |             |
                      |             |
                       -------------
                            |
                            |
                            |
                           / \
 ------------          -------------          -------------
| Lesson     |-------<| Reading     |>-------| InternalURL |
 ------------          -------------          -------------
| id         |        | id          |        | id          |
| label      |        | lessonId    |        | url         |
| summary    |        | sourceId    |        |             |
| lessonOrder|        | polytable   |        |             |
| active     |        | label       |        |             |
 ------------          -------------          -------------
                            \ /
                             |
                             |
                       ------------  
                      | Book       |
                       ------------ 
                      | id         |
                      | label      |
                      | summary    |
                      | lessonOrder|
                      | active     |
                       ------------ 

回答1:

You have at least a couple of options to preserve RI:

  1. Add several nullable FK columns in Reading, one for each reading type. Only one of these should be non-null.

    CREATE TABLE Reading (
      id INT AUTO_INCREMENT PRIMARY KEY,
      lessonId INT NOT NULL,
      bookId INT NULL,
      externalUrlId INT NULL,
      internalUrlId INT NULL,
      FOREIGN KEY (bookId) REFERENCES Book(id),
      FOREIGN KEY (externalUrlId) REFERENCES ExternalUrl(id),
      FOREIGN KEY (internalUrlId) REFERENCES InternalUrl(id)
    );
    

    Enforcing that exactly one of the foreign key columns is non-null is a task for a trigger, or else you'd have to do it in application code. But at least you can define the foreign keys.

  2. Add a supertable Readable which is a parent of each of the other specific readable types.

    CREATE TABLE Readable (
      id INT AUTO_INCREMENT PRIMARY KEY,
      readable_type CHAR(1) NOT NULL,
      UNIQUE KEY (id, readable_type)
    );
    
    CREATE TABLE Book (
      id INT PRIMARY KEY, -- not AUTO_INCREMENT
      readable_type CHAR(1) NOT NULL, -- must be 'B'
      FOREIGN KEY (id, readable_type) REFERENCES Readable(id, readable_type)
    );
    
    ... similar tables for ExternalUrl and InternalUrl...
    

    Then make Reading also reference Readable.

    CREATE TABLE Reading (
      id INT AUTO_INCREMENT PRIMARY KEY,
      lessonId INT NOT NULL,
      sourceId INT NOT NULL,
      FOREIGN KEY (sourceId) REFERENCES Readable(id)
    );
    

    I describe this solution in more detail in my answer to Why can you not have a foreign key in a polymorphic association?.