Как сделать внешний ключ способным ссылаться только на подмножество строк в целевой таблице - PullRequest
3 голосов
/ 27 сентября 2010

У меня есть таблица с первичным ключом (давайте назовем его «человек») и другая таблица, которая ссылается на него (давайте назовем его «класс», как в оценках студентов).

В таблице "grade" есть поле "grade.personid", которое является внешним ключом для "person.personid". Допустим, у «person» также есть поле «person.type» (varchar с возможными значениями «student» или «teacher» для простоты), и только студенты имеют оценки.

Как заставить базу данных отклонять любые вставки / обновления, в которых "personid" не учащегося (то есть учителя) помещается в поле "grade.personid".

В настоящее время я работаю с Sql Server 2008, но был бы заинтересован в ответах и ​​для других платформ.

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

p.s. Я знаю об ограничениях представлений, связанных со схемой, но они мне не очень нравятся, потому что они ломаются всякий раз, когда кто-либо изменяет таблицы, на которые он опирается.

Ответы [ 3 ]

4 голосов
/ 27 сентября 2010

попробуйте это:

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

поместите один FK в тип personid + и добавьте проверочное ограничение для grade.type, чтобы разрешить только тип учащегося.

2 голосов
/ 27 сентября 2010

В случае 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), чтобы получить ошибку времени выполнения в теле триггера.

0 голосов
/ 28 сентября 2010

И для справки, вот анонимная версия того, что у меня сработало:

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 (sic)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...