含常量列的多列外键创建方法及替代方案咨询
用户现有主键为两列的表SizeTypes,建表语句如下:
CREATE TABLE SizeTypes ( TypeID tinyint NOT NULL, SizeID tinyint NOT NULL, Name varchar(100) NOT NULL, CONSTRAINT PK_SizeType PRIMARY KEY (TypeID, SizeID) )
想要创建第二张表Something,尝试让外键由常量值+表中列值组成,预期建表语句如下:
CREATE TABLE Something ( ID INT IDENTITY(1,1) PRIMARY KEY, SizeTypeID_1 TINYINT, SizeTypeID_2 TINYINT, SizeTypeID_3 TINYINT, CONSTRAINT FK_Something_SizeTypes_1 FOREIGN KEY (1, SizeTypeID_1) REFERENCES SizeTypes(TypeID, SizeID), CONSTRAINT FK_Something_SizeTypes_2 FOREIGN KEY (2, SizeTypeID_2) REFERENCES SizeTypes(TypeID, SizeID), CONSTRAINT FK_Something_SizeTypes_3 FOREIGN KEY (3, SizeTypeID_3) REFERENCES SizeTypes(TypeID, SizeID) )
请问是否可以通过FOREIGN KEY实现该需求?若可以,具体如何操作?若不可行,有哪些替代方案?例如为Something表的INSERT、UPDATE操作及SizeTypes表的DELETE操作创建触发器,还有其他可选方案吗?
一、直接用FOREIGN KEY不可行
很遗憾,标准SQL以及主流数据库(比如SQL Server、MySQL、PostgreSQL等)都不支持在FOREIGN KEY约束中直接使用常量值作为外键的一部分。外键约束的列必须是当前表中实际存在的列,不能硬编码常量。你写的(1, SizeTypeID_1)这种写法会直接报错,因为数据库无法识别常量作为外键列的一部分。
二、替代方案
1. 添加计算列/持久化列(推荐)
在Something表中添加对应常量的计算列,然后把计算列和现有列组合成外键。以SQL Server为例,你可以创建持久化的计算列,这样数据库可以为这些列建立索引,提升外键约束的性能:
CREATE TABLE Something ( ID INT IDENTITY(1,1) PRIMARY KEY, SizeTypeID_1 TINYINT, -- 添加对应TypeID=1的计算列 TypeID_1 AS CAST(1 AS TINYINT) PERSISTED, SizeTypeID_2 TINYINT, TypeID_2 AS CAST(2 AS TINYINT) PERSISTED, SizeTypeID_3 TINYINT, TypeID_3 AS CAST(3 AS TINYINT) PERSISTED, -- 创建外键约束 CONSTRAINT FK_Something_SizeTypes_1 FOREIGN KEY (TypeID_1, SizeTypeID_1) REFERENCES SizeTypes(TypeID, SizeID), CONSTRAINT FK_Something_SizeTypes_2 FOREIGN KEY (TypeID_2, SizeTypeID_2) REFERENCES SizeTypes(TypeID, SizeID), CONSTRAINT FK_Something_SizeTypes_3 FOREIGN KEY (TypeID_3, SizeTypeID_3) REFERENCES SizeTypes(TypeID, SizeID) )
这种方案的好处是完全利用数据库的原生约束,不需要额外维护触发器,数据一致性由数据库保证,性能也更稳定。
2. 重构表结构(更规范的设计)
从设计角度看,Something表的SizeTypeID_1/2/3其实是重复的结构,属于多值属性。可以考虑把这部分数据拆分成子表,用一对多的关系替代:
-- 创建子表 CREATE TABLE SomethingSizeTypes ( SomethingID INT NOT NULL, TypeID TINYINT NOT NULL, SizeID TINYINT NOT NULL, PRIMARY KEY (SomethingID, TypeID), CONSTRAINT FK_SomethingSizeTypes_Something FOREIGN KEY (SomethingID) REFERENCES Something(ID), CONSTRAINT FK_SomethingSizeTypes_SizeTypes FOREIGN KEY (TypeID, SizeID) REFERENCES SizeTypes(TypeID, SizeID) ) -- 原Something表简化 CREATE TABLE Something ( ID INT IDENTITY(1,1) PRIMARY KEY -- 其他原有字段保留 )
这样设计的优势是扩展性更强——如果以后需要增加SizeTypeID_4,不需要修改表结构,直接在子表中插入数据即可。同时也更符合数据库的规范化设计,避免冗余。
3. 使用触发器(你提到的方案)
如果因为某些原因不能修改表结构或添加计算列,可以用触发器来模拟外键约束的逻辑:
- 为
Something的INSERT和UPDATE触发器:检查插入/更新的SizeTypeID_n是否在SizeTypes表中存在对应TypeID=n的记录,如果不存在则回滚操作。 - 为
SizeTypes的DELETE触发器:检查是否有Something表的记录依赖要删除的(TypeID, SizeID),如果有则回滚删除操作。
不过触发器的缺点也很明显:需要自己编写维护逻辑,容易出错,而且性能不如原生约束,排查问题也更麻烦。
内容的提问来源于stack exchange,提问作者Belphegor

