mysql - 如何将复杂的约束应用于 MySQL 中的数据库表?

标签 mysql database database-design relational-database

因为我正处于为我的一个项目设置数据库的最后阶段,我想到了一个需要添加到任务表的额外约束(见下图),但我不确定如何这可以在 MySQL 中实现。

原始数据库架构(无标记): Click here.

带标记的数据库架构: enter image description here

对于每个作业 (job),都会分配一个 WBS 代码列表 (wbscodelist)。这些列表中的每一个都包含一些适用于该作业的 WBS 代码 (wbscodeitem)。一个例子是:

Job A uses WBS Code List #1
Job B uses WBS Code List #2
Job C uses WBS Code List #1
etc.

WBS Code List #1 has codes: [100, 105, 110, 115, 120]
WBS Code List #2 has codes: [2180, 2190]
etc.

目前,task.fk_wbsCodeItemIDwbscodeitem.wbsCodeItemID 的外键(橙色标记)。

我在这里面临的问题是,任务 可能会使用不适用于该工作 的 WBS 代码。

我想在 task.fk_wbsCodeItemID 中包含一个进一步的约束,以便它可以采用的值取决于 wbscodeitem.fk_wbsCodeListIDjob.wbscodeitem。 fk_wbsCodeListID 等于该作业(以红色标记)。

如何在 MySQL 中为这个数据库模式包含这个约束?此问题是否可能是由于此数据库的当前设计造成的(我是否需要更改它)?

我知道这可能需要更多细节,因此我可以包含更多细节或在必要时进行澄清。

最佳答案

一种方法是通过受控冗余。您可以将函数依赖项 jobNumber -> fk_wbsCodeListID 反规范化为 joblocationtask,并使用复合 FK 约束来防止不一致。同样,函数依赖 wbsCodeItemID -> fk_wbsCodeListID 可以反规范化为 tasktask 中重叠的复合 FK 约束将强制执行您的要求:

CREATE TABLE `wbscodelist` (
  `wbsCodeListID` int(11) NOT NULL,
  `description` varchar(45) NOT NULL,
  PRIMARY KEY (`wbsCodeListID`)
) ENGINE=InnoDB;

CREATE TABLE `wbscodeitem` (
  `wbsCodeItemID` int(11) NOT NULL,
  `wbsCode` varchar(10) NOT NULL,
  `description` varchar(50) NOT NULL,
  `notes` varchar(80) NOT NULL,
  `fk_wbsCodeListID` int(11) NOT NULL,
  PRIMARY KEY (`wbsCodeItemID`),
  UNIQUE KEY (`wbsCodeItemID`,`fk_wbsCodeListID`),
  KEY (`fk_wbsCodeListID`),
  FOREIGN KEY (`fk_wbsCodeListID`) REFERENCES `wbscodelist` (`wbsCodeListID`) ON UPDATE CASCADE
) ENGINE=InnoDB;

CREATE TABLE `job` (
  `jobNumber` varchar(15) NOT NULL,
  `jobName` varchar(45) NOT NULL,
  `fk_wbsCodeListID` int(11) NOT NULL,
  `isActive` bit(1) NOT NULL,
  PRIMARY KEY (`jobNumber`),
  UNIQUE KEY (`jobNumber`,`fk_wbsCodeListID`),
  KEY (`fk_wbsCodeListID`),
  FOREIGN KEY (`fk_wbsCodeListID`) REFERENCES `wbscodelist` (`wbsCodeListID`) ON UPDATE CASCADE
) ENGINE=InnoDB;

CREATE TABLE `joblocation` (
  `jobLocationID` int(11) NOT NULL,
  `roomNumber` varchar(25) NOT NULL,
  `fk_jobNumber` varchar(15) NOT NULL,
  `fk_wbsCodeListID` int(11) NOT NULL,
  PRIMARY KEY (`jobLocationID`),
  KEY (`fk_jobNumber`,`fk_wbsCodeListID`),
  KEY (`jobLocationID`,`fk_wbsCodeListID`),
  FOREIGN KEY (`fk_jobNumber`, `fk_wbsCodeListID`) REFERENCES `job` (`jobNumber`, `fk_wbsCodeListID`) ON UPDATE CASCADE
) ENGINE=InnoDB;

CREATE TABLE `task` (
  `taskID` int(11) NOT NULL,
  `fk_JobLocationID` int(11) NOT NULL,
  `fk_JobNumber` varchar(15) NOT NULL,
  `fk_wbsCodeItemID` int(11) NOT NULL,
  `fk_wbsCodeListID` int(11) NOT NULL,
  PRIMARY KEY (`taskID`),
  KEY (`fk_wbsCodeItemID`,`fk_wbsCodeListID`),
  KEY (`fk_JobLocationID`,`fk_wbsCodeListID`),
  FOREIGN KEY (`fk_JobLocationID`, `fk_wbsCodeListID`) REFERENCES `joblocation` (`jobLocationID`, `fk_wbsCodeListID`) ON UPDATE CASCADE,
  FOREIGN KEY (`fk_wbsCodeItemID`, `fk_wbsCodeListID`) REFERENCES `wbscodeitem` (`wbsCodeItemID`, `fk_wbsCodeListID`) ON UPDATE CASCADE
) ENGINE=InnoDB;

注意复合索引以匹配复合 FK 约束。

另一种选择是创建触发器来检查插入/更新的 FK 值是否通过连接相关:

DELIMITER ;;

CREATE TRIGGER check_task_insert BEFORE INSERT ON task
    FOR EACH ROW
    BEGIN
        IF NOT EXISTS (
            SELECT 1
            FROM joblocation loc
            JOIN job ON loc.fk_jobNumber = job.jobNumber
            JOIN wbscodeitem itm ON job.fk_wbsCodeListID = itm.fk_wbsCodeListID
            WHERE loc.jobLocationID = new.fk_jobLocationID
            AND itm.wbsCodeItemID = new.fk_wbsCodeItemID
        ) THEN
            SIGNAL SQLSTATE '45000'   
            SET MESSAGE_TEXT = 'fk_wbsCodeItemID doesn\'t match fk_wbsCodeListID of associated job';
        END IF;
    END;
;;

CREATE TRIGGER check_task_update BEFORE UPDATE ON task
    FOR EACH ROW
    BEGIN
        IF NOT EXISTS (
            SELECT 1
            FROM joblocation loc
            JOIN job ON loc.fk_jobNumber = job.jobNumber
            JOIN wbscodeitem itm ON job.fk_wbsCodeListID = itm.fk_wbsCodeListID
            WHERE loc.jobLocationID = new.fk_jobLocationID
            AND itm.wbsCodeItemID = new.fk_wbsCodeItemID
        ) THEN
            SIGNAL SQLSTATE '45000'   
            SET MESSAGE_TEXT = 'fk_wbsCodeItemID doesn\'t match fk_wbsCodeListID of associated job';
        END IF;
    END;
;;

DELIMITER ;

关于mysql - 如何将复杂的约束应用于 MySQL 中的数据库表?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42571843/

相关文章:

mysql - Laravel - 违反完整性约束 : 1452 Cannot add or update a child row

php - 清理输入到 Mysql 数据库的函数

php - 如何检查我的服务器上的 query_cache_type 值

database - 如何实现多对多对多的数据库关系?

mysql - 订单和子订单的数据库设计

mysql - 如何保护用户名、密码、api_keys

php - MySQL插入多行

javascript - PHP:看似简单的 IF 语句,但似乎不起作用

sql - 将聚簇索引应用于 SQL Server 2012 中的 View

c# - EF : Database design issues regarding cross database relations