Как лучше всего обеспечить одноуровневую рекурсию в таблице SQL?

Допустим, у вас есть таблица филиалов в вашей организации. Некоторые из них являются «основными» филиалами, а другие - дополнительными офисами, которые объединяются в основной филиал. Помимо этого различия, которое влияет только на несколько вещей в системе, все ветви являются одноранговыми и имеют одинаковые атрибуты (адрес и т. д.). Один из способов смоделировать это в виде таблицы:

CREATE TABLE Branch (
    branch_id INT NOT NULL PRIMARY KEY IDENTITY(1,1),
    branch_name VARCHAR(80) NOT NULL,
    street VARCHAR(80) NULL,
    city VARCHAR(30) NULL,
    state CHAR(2) NULL,
    zip CHAR(5) NULL,
    is_satellite_office BIT NOT NULL DEFAULT(0),
    satellite_to_branch_id INT NULL REFERENCES Branch(branch_id)
)

Где is_satellite_office = 1, если эта запись является сателлитом другой ветви, а satellite_to_branch_id относится к той ветви, сателлитом которой вы являетесь, если таковая имеется.

Достаточно легко наложить ограничение на таблицу, чтобы эти два столбца согласовывались с любой записью:

CONSTRAINT [CK_Branch] CHECK 
  (
    (is_satellite_office = 0 AND satellite_to_branch_id IS NULL) 
    OR (is_satellite_office = 1 AND satellite_to_branch_id IS NOT NULL)
  )

Однако на самом деле мне нужен способ гарантировать, что эта рекурсия идет только на уровень один ... то есть, если я указываю на ветку как на своего родителя, она не должна иметь самого родителя, а ее значение для is_satellite_office должно быть 0. Другими словами, мне не нужна полностью рекурсивная древовидная структура, я просто хочу ограничить ее одним отношением родитель / потомок. Вот как я собираюсь написать код, и если есть способ обеспечить его соблюдение в базе данных, который не будет работать как полная чушь, я бы хотел.

Любые идеи? Я работаю над MSSQL 2005, но предпочтительны общие (не зависящие от производителя) решения. И никаких триггеров применять не нужно, если действительно нет другого способа сделать это.

Обновлено: Для ясности, satellite_to_branch_id - это рекурсивный указатель на другую запись в той же таблице Branch. Я знаю, что мог бы удалить is_satellite_office BIT и полагаться на IsNull(satellite_to_branch_id), чтобы дать мне ту же информацию, но я считаю, что немного яснее быть явным, и, кроме того, это не суть вопроса. Я действительно ищу чистый способ ограничения SQL, чтобы предотвратить глубину рекурсии больше 1.

ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
1
0
726
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

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

+1. Вроде того, чего я боялся, но я надеюсь, что у кого-то есть крутой трюк, чтобы это сделать.

Ian Varley 08.01.2009 19:36

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

Otávio Décio 08.01.2009 19:39

А как насчет этой немного другой структуры?

CREATE TABLE Branch (
    branch_id INT NOT NULL PRIMARY KEY IDENTITY(1,1),
    branch_name VARCHAR(80) NOT NULL,
    street VARCHAR(80) NULL,
    city VARCHAR(30) NULL,
    state CHAR(2) NULL,
    zip CHAR(5) NULL,
    parent_id int NULL
)

PARENT_ID просто указывает на BRANCH_ID другой записи в той же таблице. Если он равен нулю, то вы знаете, что у него нет родителей.

Затем, чтобы получить один уровень рекурсии, вы можете просто один раз присоединить таблицу к самой себе, например:

SELECT
  PARENT.BRANCH_NAME AS PARENT_BRANCH
 ,CHILD.BRANCH_NAME AS CHILD_BRANCH
FROM
  BRANCH PARENT
 ,BRANCH CHILD
WHERE CHILD.PARENT_ID PARENT.BRANCH_ID

Если вы хотите обеспечить один уровень глубины в своем дереве, создайте триггер при вставке / обновлении, который вызовет исключение, если этот запрос что-либо вернет.

SELECT *
FROM
  BRANCH B1
 ,BRANCH B2
 ,BRANCH B3
WHERE B1.PARENT_ID = :NEW.NEW_PARENT_ID
  AND B2.PARENT_ID = B1.BRANCH_ID
  AND B2.PARENT_ID = B3.BRANCH_ID;

Извините, если мое описание не было ясным; это именно то, что он уже делает в табличной форме (я просто назвал его иначе, чем "parent_id"). Вопрос состоял только в том, как обеспечить выполнение одинарной рекурсии в SQL, в идеале без триггера. Но спасибо за триггерное решение!

Ian Varley 08.01.2009 19:43

Разве вам не разрешено ссылаться на хранимую процедуру в вашем ограничении? Вы можете в PostgreSQL, поэтому я был бы удивлен, если бы 2005 г. не позволял этого.

Это не должно сильно повредить производительности. В конце концов, ваш SP выйдет из строя максимум после одной итерации, и как часто вы будете вставлять / обновлять ветки? (Если чаще, чем один раз в день, дайте мне знать, чтобы я мог покупать акции.)

Kev 08.01.2009 20:05

Согласен ... Я не видел, чтобы это было сделано в MS SQL Server, и я не знаю, разрешено ли это. Кто-нибудь еще знает?

Ian Varley 08.01.2009 20:30
Ответ принят как подходящий

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

Одна ошибка, которая меня замедлила, UDF увидит, что строка была вставлена.

cmsjr 08.01.2009 20:47

Просто попробовал, и это сработало. Мое ограничение проверки передает значение столбца родительского указателя в UDF, который проверяет, есть ли у записи, на которую указывает этот идентификатор, родительский элемент, и возвращает соответствующий результат. Спасибо!

Ian Varley 12.01.2009 17:55

Другие вопросы по теме