Ограничение отношений один-ко-многим - PullRequest
4 голосов
/ 15 апреля 2009

У нас есть две таблицы с отношением один ко многим. Мы хотели бы ввести ограничение, что по крайней мере одна дочерняя запись существует для данной родительской записи.

Возможно ли это?

Если нет, измените ли вы схему немного сложнее для поддержки такого ограничения? Если да, то как бы вы это сделали?

Редактировать: я использую SQL Server 2005

Ответы [ 5 ]

7 голосов
/ 15 апреля 2009

Такое ограничение невозможно с точки зрения схемы, поскольку вы сталкиваетесь со сценарием типа "курица или яйцо". При таком сценарии, когда я вставляю в родительскую таблицу, у меня должна быть строка в дочерней таблице, но у меня не может быть строки в дочерней таблице, пока в родительской таблице нет строки.

Это что-то лучше на стороне клиента.

1 голос
/ 15 апреля 2009

Это возможно, если ваш сервер поддерживает отложенные ограничения, как это делает PostgreSQL.

0 голосов
/ 22 сентября 2016

Я столкнулся с этой проблемой, и у меня есть решение, реализованное в Oracle rel.11.2.4.

  1. Чтобы гарантировать, что у каждого ребенка есть родитель, я применил типичное ограничение внешнего ключа от FK ребенка до PK родителя. - здесь нет волшебства
  2. Чтобы у каждого родителя был хотя бы один ребенок, я сделал следующее:

Создайте функцию, которая принимает родительский PK и возвращает COUNT потомков для этого PK. - Я гарантирую, что исключения NO_DATA_FOUND возвращают 0.

Создайте виртуальный столбец CHILD_COUNT в родительской таблице и вычислите его как результат функции.

Создание отложенного ограничения CHECK для виртуального столбца CHILD_COUNT с критериями CHILD_COUNT > 0

Работает следующим образом:

  • Если родительская строка вставлена, а дочерние элементы еще не существуют. затем, если эта строка зафиксирована, ограничение CHILD_COUNT > 0 CHECK не выполняется и транзакция откатывается.
  • Если родительская строка вставлена, а соответствующая дочерняя строка вставлена ​​в ту же транзакцию; тогда все ограничения целостности удовлетворяются при выдаче COMMIT.
  • Если дочерняя строка вставлена ​​в соответствии с существующей родительской строкой, то виртуальный столбец CHILD_COUNT пересчитывается на COMMIT и нарушения целостности не происходит.
  • Любое удаление родительских строк должно каскадно связываться с дочерними, иначе потерянные дочерние строки будут нарушать ограничение FOREIGN KEY при фиксации транзакции удаления. (как обычно)
  • Любое удаление дочерних строк должно оставлять как минимум одного дочернего для каждого родителя, в противном случае проверочное ограничение CHILD_COUNT будет нарушаться при фиксации транзакции.

ПРИМЕЧАНИЕ: мне не понадобится виртуальный столбец, если Oracle разрешит основанные на пользовательских функциях CHECK constraints в rel.11.2.4.

0 голосов
/ 01 ноября 2015

Это на самом деле не то, что «лучше обеспечивается на стороне клиента», а то, что нецелесообразно применять в определенных реализациях базы данных. Реально работа ДОЛЖНА принадлежать базе данных, и должен работать хотя бы один из обходных путей, приведенных ниже.

В конечном счете, вы хотите ограничить родителя ребенком. Это гарантирует, что ребенок существует. К сожалению, это вызывает проблему куриного яйца, потому что дети должны указывать на одного и того же родителя, вызывая конфликт ограничений.

Для решения проблемы без видимых побочных эффектов в остальной части вашей системы требуется одна из двух способностей - ни одна из которых не обнаружена в SQL Server.

1) Отложенная проверка ограничений - Это вызывает проверку ограничений в конце транзакции. Обычно они происходят в конце утверждения. В этом корень проблемы с куриным яйцом, так как она не позволяет вставить первый дочерний или родительский ряд из-за отсутствия другого, и это решает ее.

2) Вы можете использовать CTE для вставки первого дочернего элемента, где CTE висит от оператора, который вставляет родительский элемент (или наоборот). При этом обе строки вставляются в одну и ту же инструкцию, вызывая эффект, аналогичный отложенной проверке ограничения.

3) Без них у вас нет выбора, кроме как разрешить пустые значения в одной из ссылок, чтобы вы могли вставить эту строку без проверки зависимостей. Затем вы должны вернуться и обновить ноль со ссылкой на второй ряд. Если вы используете эту технику, вы должны быть осторожны, чтобы остальная часть системы ссылалась на родительскую таблицу через представление, которое скрывает все строки с нулем в дочернем столбце ссылок.

В любом случае удаление дочерних элементов столь же сложно, поскольку вы не можете удалить дочерний элемент, который доказывает, что хотя бы один из них существует, если вы сначала не обновите родительский элемент, чтобы указать на дочерний элемент, который не будет удален.

Когда вы собираетесь удалить последнего ребенка, вы должны либо выдать ошибку, либо удалить родителя одновременно. Ошибка произойдет автоматически, если вы не установите родительский указатель на ноль в первую очередь (или отложите проверку). Если вы отложите (или установите для дочернего указателя значение NULL), вы сможете удалить дочернего элемента, а затем родительский элемент также может быть удален.

Я буквально исследовал это в течение многих лет, и я наблюдаю за каждой версией SQL Server для облегчения этой проблемы, поскольку это так часто встречается.

ПОЖАЛУЙСТА Как только у кого-то есть практическое решение, пожалуйста, напишите!

P.S. Вам нужно либо использовать составной ключ при обращении к строке подтверждения ребенка от родителя, либо с помощью триггера, чтобы убедиться, что ребенок, предоставляющий подтверждение, действительно считает эту строку своим родителем.

P.P.S Хотя верно, что null никогда не должен быть видимым для остальной части вашей системы, если вы выполняете и вставку, и обновление в одной и той же транзакции, это зависит от поведения, которое может дать сбой. Смысл этого ограничения состоит в том, чтобы гарантировать, что сбой логики не оставит вашу базу данных в недопустимом состоянии. Защищая таблицу с помощью представления, скрывающего пустые значения, любая недопустимая строка не будет видна. Очевидно, что ваша логика вставки должна учитывать возможность того, что такая строка может существовать, но она все равно нуждается в знаниях и ничего больше не нужно знать.

0 голосов
/ 15 апреля 2009

Как насчет простого необнуляемого столбца?

Create Table ParentTable
(
ParentID
ChildID not null,
Primary Key (ParentID), 
Foreign Key (ChildID ) references Childtable (ChildID));
)

Если ваша бизнес-логика позволяет, и у вас есть значения по умолчанию, которые вы можете запрашивать из базы данных для каждой новой родительской записи, вы можете затем использовать before insert trigger в родительской таблице для заполнения необнуляемого дочернего столбца.

CREATE or REPLACE TRIGGER trigger_name
BEFORE INSERT
    ON ParentTable
    FOR EACH ROW 
BEGIN

    -- ( insert new row into ChildTable )
    -- update childID column in ParentTable 

END;
...