Предположим, у нас есть таблица, которая имеет ограничение внешнего ключа, например:
CREATE TABLE Foo
(FooId BIGINT PRIMARY KEY,
ParentFooId BIGINT,
FOREIGN KEY([ParentFooId]) REFERENCES Foo ([FooId]) )
INSERT INTO Foo (FooId, ParentFooId)
VALUES (1, NULL), (2, 1), (3, 2)
UPDATE Foo SET ParentFooId = 3 WHERE FooId = 1
Эта таблица будет иметь следующие записи:
FooId ParentFooId
----- -----------
1 3
2 1
3 2
Бывают случаи, когда такой дизайн может иметь смысл (например, типичные отношения «сотрудник-босс-сотрудник»), и в любом случае: я нахожусь в ситуации, когда это происходит в моей схеме.
Такая конструкция, к сожалению, допускает цикличность в записях данных, как показано в примере выше.
Мой вопрос:
- Является ли это возможно , чтобы написать ограничение , которое проверяет это? а также
- Является ли это возможно , чтобы написать ограничение , которое проверяет это? (если нужно только на определенную глубину)
Для части (2) этого вопроса может быть уместно упомянуть, что я ожидаю только сотни или, возможно, в некоторых случаях тысячи записей в моей таблице, обычно не вложенных глубже, чем примерно на 5-10 уровней.
PS. MS SQL Server 2008
Обновление 14 марта 2012 г.
Было несколько хороших ответов. Я теперь принял тот, который помог мне понять упомянутую возможность / осуществимость. Есть и несколько отличных ответов, некоторые с предложениями по реализации, так что, если вы попали сюда с тем же вопросом, посмотрите на все ответы;)
HIERARCHYID
которые, по-видимому, являются собственной реализацией модели вложенного набора в MSSQL2008.Я видел 2 основных способа обеспечить это:
1, старый способ:
Столбец FooHierarchy будет содержать значение, подобное этому:
Где числа отображаются в столбце FooId. Затем вы должны убедиться, что столбец Hierarchy оканчивается на «| id», а остальная часть строки соответствует FooHieratchy of PARENT.
2, новый способ:
SQL Server 2008 имеет новый тип данных, называемый HierarchyID , который делает все это за вас.
Он работает на том же принципе, что и OLD, но эффективно обрабатывается SQL Server и подходит для использования в качестве ЗАМЕНЫ для вашего столбца «ParentID».
источник
HIERARCHYID
предотвращает создание петель иерархии?Это возможно: вы можете вызывать скалярный UDF из ограничения CHECK, и он может обнаруживать циклы любой длины. К сожалению, этот подход чрезвычайно медленный и ненадежный: у вас могут быть ложные срабатывания и ложные отрицания.
Вместо этого я бы использовал материализованный путь.
Другой способ избежать циклов - использовать CHECK (ID> ParentID), что, вероятно, также не очень выполнимо.
Еще один способ избежать циклов состоит в том, чтобы добавить еще два столбца, LevelInHierarchy и ParentLevelInHierarchy, иметь (ParentID, ParentLevelInHierarchy) ссылку на (ID, LevelInHierarchy) и иметь CHECK (LevelInHierarchy> ParentLevelInHierarchy).
источник
Я считаю, что это возможно:
Возможно, я что-то пропустил (извините, я не смог проверить это полностью), но, похоже, это работает.
источник
Вот еще один вариант: триггер, который разрешает многострочные обновления и обеспечивает отсутствие циклов. Он работает путем обхода цепочки предков, пока не найдет корневой элемент (с родительским NULL), что доказывает отсутствие цикла. Он ограничен 10 поколениями, поскольку цикл, конечно, бесконечен.
Он работает только с текущим набором измененных строк, поэтому, если обновления не затрагивают огромное количество очень глубоких элементов в таблице, производительность не должна быть слишком плохой. Он должен пройти весь путь вверх по цепочке для каждого элемента, так что это окажет некоторое влияние на производительность.
По-настоящему «интеллектуальный» триггер будет искать циклы напрямую, проверяя, достиг ли элемент сам себя, а затем освобождая его. Однако это требует проверки состояния всех ранее найденных узлов во время каждого цикла и, таким образом, требует цикла WHILE и большего количества кодирования, чем я хотел сделать прямо сейчас. Это не должно быть на самом деле дороже, потому что нормальная операция будет состоять в отсутствии циклов, и в этом случае она будет работать быстрее только с предыдущим поколением, а не со всеми предыдущими узлами во время каждого цикла.
Мне бы очень хотелось узнать мнение @AlexKuznetsov или кого-либо еще о том, как это будет происходить в условиях изоляции. Я подозреваю, что это не очень хорошо, но хотел бы понять это лучше.
Обновить
Я разобрался, как избежать дополнительного объединения обратно во вставленную таблицу. Если кто-нибудь найдет лучший способ сделать GROUP BY для обнаружения тех, которые не содержат NULL, пожалуйста, дайте мне знать.
Я также добавил переключатель READ COMMITTED, если текущий сеанс находится на уровне SNAPSHOT ISOLATION. Это предотвратит несоответствия, хотя, к сожалению, приведет к усилению блокировки. Это как бы неизбежно для поставленной задачи.
источник
Если ваши записи вложены более чем в один уровень, ограничение работать не будет (я предполагаю, что вы имеете в виду, например, что запись 1 является родительской для записи 2, а запись 3 является родительской для записи 1). Единственный способ сделать это - либо в родительском коде, либо с помощью триггера, но если вы смотрите на большую таблицу и несколько уровней, это будет довольно интенсивно.
источник