Implementing a “Both, Either-or, but Not Null” Requirement in a Database

后端 未结 4 1456
谎友^
谎友^ 2021-01-21 16:11

I have requirement for a web app that states that a user should be able to either upload an instruction document(s) (.pdf, .doc, .txt) or provide text for the instructions. The

4条回答
  •  深忆病人
    2021-01-21 16:39

    Ypercube's answer is fine, except this can, in fact, be done purely through declarative integrity while keeping separate tables. The trick is to combine deferred circular FOREIGN KEYs with a little bit of creative denormalization:

    enter image description here

    CREATE TABLE Instruction (
        InstructionId INT PRIMARY KEY,
        TextId INT UNIQUE,
        DocumentId INT UNIQUE,
        CHECK (
            (TextId IS NOT NULL AND InstructionId = TextId)
            OR (DocumentId IS NOT NULL AND InstructionId = DocumentId)
        )
    );
    
    CREATE TABLE Text (
        InstructionId INT PRIMARY KEY,
        FOREIGN KEY (InstructionId) REFERENCES Instruction (TextId) ON DELETE CASCADE
    );
    
    CREATE TABLE Document (
        InstructionId INT PRIMARY KEY,
        FOREIGN KEY (InstructionId) REFERENCES Instruction (DocumentId) ON DELETE CASCADE
    );
    
    ALTER TABLE Instruction ADD FOREIGN KEY (TextId) REFERENCES Text DEFERRABLE INITIALLY DEFERRED;
    ALTER TABLE Instruction ADD FOREIGN KEY (DocumentId) REFERENCES Document DEFERRABLE INITIALLY DEFERRED;
    

    Inserting Text is done like this:

    INSERT INTO Instruction (InstructionId, TextId) VALUES (1, 1);
    INSERT INTO Text (InstructionId) VALUES (1);
    COMMIT;
    

    Inserting Document like this:

    INSERT INTO Instruction (InstructionId, DocumentId) VALUES (2, 2);
    INSERT INTO Document (InstructionId) VALUES (2);
    COMMIT;
    

    And inserting both Text and Document like this:

    INSERT INTO Instruction (InstructionId, TextId, DocumentId) VALUES (3, 3, 3);
    INSERT INTO Text (InstructionId) VALUES (3);
    INSERT INTO Document (InstructionId) VALUES (3);
    COMMIT;
    

    However, trying to insert Instruction alone fails on commit:

    INSERT INTO Instruction (InstructionId, TextId) VALUES (4, 4);
    COMMIT; -- Error (FOREIGN KEY violation).
    

    Attempting to insert the "mismatched type" also fails on commit:

    INSERT INTO Document (InstructionId) VALUES (1);
    COMMIT; -- Error (FOREIGN KEY violation).
    

    And of course, trying to insert bad values into Instruction fails (this time before commit):

    INSERT INTO Instruction (InstructionId, TextId) VALUES (5, 6); -- Error (CHECK violation).
    INSERT INTO Instruction (InstructionId) VALUES (7); -- Error (CHECK violation).
    

提交回复
热议问题