如何使外键只能引用目标表中的行子集

时间:2010-09-27 15:46:06

标签: sql-server foreign-keys

我有一个带有主键的表(让我们称之为“人”),以及另一个引用它的表(我们称之为“成绩”,如学生成绩)。

表“等级”具有字段“grade.personid”,它是“person.personid”的外键。让我们说“人”也有字段“person.type”(varchar与“学生”或“老师”的可能值一样简单),只有学生才有成绩。

如何使数据库拒绝任何将非学生(即教师)的“personid”放入“grade.personid”字段的插入/更新。

我目前正在使用Sql Server 2008,但也会对其他平台的答案感兴趣。

                 [ grade  ]
[ person ]       [--------]
[--------]       [gradeid ]
[personid] <-FK- [personid]
[type    ]       [data    ]
[name    ]

P.S。我知道模式绑定视图的约束,但并不真正喜欢它们,因为只要有人修改了他们所依赖的表,它就会中断。

3 个答案:

答案 0 :(得分:4)

试试这个:

                 [ grade  ]
[ person ]       [--------]
[--------]       [gradeid ]
[personid] <-FK- [personid]
[type    ] <-FK- [type    ]
[name    ]       [data    ]

在personid +类型上放置一个FK,并在grade.type上添加一个检查约束,仅允许学生类型。

答案 1 :(得分:2)

如果是SQL Server,可以通过定义INSTEAD OF触发器来实现所需的逻辑。例如,对于Mysql服务器,您需要定义BEFORE触发器。

<强>更新即可。触发器示例(SQL服务器)

CREATE TRIGGER ON [grades] INSTEAD INSERT
AS
BEGIN
   IF NOT EXISTS(   
   SELECT 1 FROM [person] WHERE personid = inserted.person_id AND person.type  = 'student'
   )
   BEGIN
      RAISERROR ('Invalid person type', 10, 1);
   END;

   INSERT INTO [grades] SELECT field1, field2, ... FROM inserted;
END
GO

对于mysql,它应该是CREATE TRIGGER ... BEFORE INSERT。此外,mysql没有RAISEERROR的模拟,因此您需要生成错误以防止插入。通常,我使用INSERT INTO not_existing_table(id) VALUES(1,2)来获取触发器主体中的运行时错误。

答案 2 :(得分:0)

记录中,这是一个对我有用的匿名版本:

CREATE TRIGGER trigRestrictGradeToStudents
ON dbo.grade
FOR INSERT, UPDATE
AS
IF NOT EXISTS (
    SELECT person.personid
    FROM person
        INNER JOIN inserted ON person.personid = inserted.personid
    WHERE (type = 'student')        
)
BEGIN
    ROLLBACK TRANSACTION --prevent the insert/update from proceeding
    RAISERROR ('Only students may have grade records', 11, 1)
END

严重性设置为11,使其显示在“事务在触发器中结束之前的错误消息中。批处理已中止。”

另见RAISERROR(原文如此)