Як мати стосунки з багатьма з привілейованою дитиною?


22

Я хочу створити стосунки «один до багатьох», в яких для кожного з батьків один або нуль дітей позначається як «улюблений». Однак не кожен батько матиме дитину. (Подумайте про батьків як питання на цьому веб-сайті, дітей як відповіді, а улюблену як прийняту відповідь.) Наприклад,

TableA
    Id            INT PRIMARY KEY

TableB
    Id            INT PRIMARY KEY
    Parent        INT NOT NULL FOREIGN KEY REFERENCES TableA.Id

Як я це бачу, я можу додати наступний стовпець до таблиціA:

    FavoriteChild INT NULL FOREIGN KEY REFERENCES TableB.Id

або наступний стовпець до таблиціB:

    IsFavorite    BIT NOT NULL

Проблема першого підходу полягає в тому, що він вводить нульовий зовнішній ключ, який, наскільки я розумію, не в нормованій формі. Проблема другого підходу полягає в тому, що потрібно зробити більше роботи, щоб забезпечити, щонайменше, одну дитину.

Які критерії я повинен використовувати, щоб визначити, який підхід використовувати? Або є інші підходи, які я не розглядаю?

Я використовую SQL Server 2012.

Відповіді:


19

Інший спосіб (без Нулів і без циклів у FOREIGN KEYвідносинах) - мати третю таблицю для зберігання "улюблених дітей". У більшості СУБД вам потрібно буде додаткове UNIQUEобмеження TableB.

@Aaron швидше визначив, що вищевказана угода про іменування є досить громіздкою і може призвести до помилок. Зазвичай це краще (і буде зберігати вас здорово), якщо у вас немає Idстовпців по всьому столу і якщо стовпці (що з’єднуються) мають однакові назви у багатьох таблицях, що з'являються. Отже, ось перейменування:

Parent
    ParentID        INT NOT NULL PRIMARY KEY

Child
    ChildID         INT NOT NULL PRIMARY KEY
    ParentID        INT NOT NULL FOREIGN KEY REFERENCES Parent (ParentID)
    UNIQUE (ParentID, ChildID)

FavoriteChild
    ParentID        INT NOT NULL PRIMARY KEY
    ChildID         INT NOT NULL 
    FOREIGN KEY (ParentID, ChildID) 
        REFERENCES Child (ParentID, ChildID)

У SQL-сервері (який ви використовуєте) у вас також є можливість IsFavoriteвказати бітову колонку. Унікальна улюблена дитина на одного батька може бути досягнута за допомогою відфільтрованого унікального індексу:

Parent
    ParentID        INT NOT NULL PRIMARY KEY

Child
    ChildID         INT NOT NULL PRIMARY KEY
    ParentID        INT NOT NULL FOREIGN KEY REFERENCES Parent (ParentID)
    IsFavorite      BIT NOT NULL

CREATE UNIQUE INDEX is_FavoriteChild
  ON Child (ParentID)
  WHERE IsFavorite = 1 ;

І головна причина того, що ваш варіант 1 не рекомендується, принаймні не в SQL-сервері, полягає в тому, що візерунок кругових шляхів у посиланнях іноземних ключів має деякі проблеми.

Прочитайте досить стару статтю: SQL By Design: Circular Reference

Вставляючи або видаляючи рядки з двох таблиць, ви стикаєтеся з проблемою "курка-яйце". Яку таблицю я повинен вставити першою - не порушуючи жодних обмежень?

Для того, щоб вирішити це, вам потрібно визначити принаймні один стовпчик, який є нульовим. (Гаразд, технічно цього не потрібно, ви можете мати всі стовпці як NOT NULLтільки в СУБД, наприклад Postgres та Oracle, які реалізували відкладені обмеження. Див. Відповідь Ервіна в аналогічному запитанні: Складне обмеження зовнішнього ключа в SQLAlchemy про те, як це можна зробити в Postgres). І все-таки ця установка відчуває себе катанням на тонкому льоду.

Перевірте також майже однакове запитання на SO (але для MySQL) У SQL, чи добре, щоб дві таблиці посилалися одна на одну? де моя відповідь майже однакова. Однак MySQL не має часткових індексів, тому єдиними життєздатними варіантами є нульовий FK та додаткове рішення таблиці.


9

Це залежить від вашого пріоритету. Ви хочете уникати роботи чи хочете дотримуватися найсуворіших правил нормалізації?

Особисто я вважаю, що краще мати IsFavoriteдитячий стіл, і я хотів би взяти участь у роботі, щоб переконатися, що щонайменше одна дитина на кожного з батьків є улюбленим батьком. Основна причина не має нічого спільного з нульовою зовнішньою ключовою річчю: мені взагалі не подобається ідея зовнішніх ключів, що вказують в обох напрямках.

@ пропозиція ypercube також є хорошим компромісом.

В сторону, будь ласка, будь ласка, будь ласка, не засмічуйте вашу схему безглуздими назвами стовпців, як Id. Я набагато скоріше побачив Idби, щоб ім'я було осмисленим у всій схемі. Чи визначає він автора? Гаразд, зателефонуйте AuthorID. Чи представляє він товар? Добре, ProductID. Це працівник, а в деяких випадках посилається на керівника? Гаразд, EmployeeIDі ManagerIDдля мене більше сенсу, ніж IDі Parent. Хоча це може здатися логічним залишити це (і надмірно це вкласти), коли ви почнете писати складні приєднання (або розміщувати тут запити), ви, безумовно, відчуєте певну лайку, коли ви намагаєтеся змінити інженеру купу приєднань до цієї точки. до стовпців, як a.Parent = b.ID... blecch.


1

Дані належать дочірній таблиці. Ми тримаємо це правильно за допомогою тригера на столі, щоб переконатися, що одна і єдина запис позначена як обрана (або в нашому випадку як бажана адреса).

Однак ідея @ ypercube про окрему таблицю також хороша.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.