Як відобразити співвідношення IS-A у базі даних?


26

Розглянемо наступне:

entity User
{
    autoincrement uid;
    string(20) name;
    int privilegeLevel;
}

entity DirectLoginUser
{
    inherits User;
    string(20) username;
    string(16) passwordHash;
}

entity OpenIdUser
{
    inherits User;
    //Whatever attributes OpenID needs... I don't know; this is hypothetical
}

Різні типи користувачів (користувачі прямого входу та користувачі OpenID) відображають відносини IS-A; а саме, що обидва типи користувачів - це користувачі. Тепер є кілька способів, як це можна представити в RDBMS:

Шлях перший

CREATE TABLE Users
(
    uid INTEGER AUTO_INCREMENT NOT NULL,
    name VARCHAR(20) NOT NULL,
    privlegeLevel INTEGER NOT NULL,
    type ENUM("DirectLogin", "OpenID") NOT NULL,
    username VARCHAR(20) NULL,
    passwordHash VARCHAR(20) NULL,
    //OpenID Attributes
    PRIMARY_KEY(uid)
)

Шлях другий

CREATE TABLE Users
(
    uid INTEGER AUTO_INCREMENT NOT NULL,
    name VARCHAR(20) NOT NULL,
    privilegeLevel INTEGER NOT NULL,
    type ENUM("DirectLogin", "OpenID") NOT NULL,
    PRIMARY_KEY(uid)
)

CREATE TABLE DirectLogins
(
    uid INTEGER NOT_NULL,
    username VARCHAR(20) NOT NULL,
    passwordHash VARCHAR(20) NOT NULL,
    PRIMARY_KEY(uid),
    FORIGEN_KEY (uid) REFERENCES Users.uid
)

CREATE TABLE OpenIDLogins
(
    uid INTEGER NOT_NULL,
    // ...
    PRIMARY_KEY(uid),
    FORIGEN_KEY (uid) REFERENCES Users.uid
)

Шлях третій

CREATE TABLE DirectLoginUsers
(
    uid INTEGER AUTO_INCREMENT NOT NULL,
    name VARCHAR(20) NOT NULL,
    privlegeLevel INTEGER NOT NULL,
    username VARCHAR(20) NOT NULL,
    passwordHash VARCHAR(20) NOT NULL,
    PRIMARY_KEY(uid)
)

CREATE TABLE OpenIDUsers
(
    uid INTEGER AUTO_INCREMENT NOT NULL,
    name VARCHAR(20) NOT NULL,
    privlegeLevel INTEGER NOT NULL,
    //OpenID Attributes
    PRIMARY_KEY(uid)
)

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

Мій приклад із реального світу - це не користувачі, що мають різні приклади входу; Мене цікавить, як моделювати цей взаємозв'язок у загальному випадку.


Я відредагував свою відповідь, щоб включити підхід, запропонований у коментарях Джоела Брауна. Це має працювати для вас. Якщо ви шукаєте додаткові пропозиції, я би повторно позначив ваше запитання, щоб вказати, що шукаєте відповідь, пов’язану з MySQL.
Нік Чаммас

Зауважте, це дійсно залежить від того, як стосунки використовуються в решті бази даних. Взаємовідносини, в яких беруть участь дочірні суб'єкти, вимагають зовнішніх ключів лише до цих таблиць і забороняють відображати в єдиній об'єднавчій таблиці без деякої характесті.
белдаз

У об’єктно-реляційних базах даних, таких як PostgreSQL, існує насправді четвертий спосіб: ви можете оголосити таблицю ІНЕРИТИ з іншої таблиці. postgresql.org/docs/current/static/tutorial-inheritance.html
MarkusSchaber

Відповіді:


16

Шлях другий - правильний шлях.

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

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

Наприклад, MySQL DDL виглядатиме приблизно так:

CREATE TABLE Users
(
      uid               INTEGER AUTO_INCREMENT NOT NULL
    , type              ENUM("DirectLogin", "OpenID") NOT NULL
    // ...

    , PRIMARY_KEY(uid)
);

CREATE TABLE DirectLogins
(
      uid               INTEGER NOT_NULL
    , type              ENUM("DirectLogin") NOT NULL
    // ...

    , PRIMARY_KEY(uid)
    , FORIGEN_KEY (uid, type) REFERENCES Users (uid, type)
);

CREATE TABLE OpenIDLogins
(
      uid               INTEGER NOT_NULL
    , type              ENUM("OpenID") NOT NULL
    // ...

    PRIMARY_KEY(uid),
    FORIGEN_KEY (uid, type) REFERENCES Users (uid, type)
);

(На інших платформах ви б використовували CHECKобмеження замість ENUM.) MySQL підтримує складені зовнішні ключі, тому це повинно працювати для вас.

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

Третій спосіб змушує будь-які запити, на які посилаються користувачі, перевіряти обидві таблиці. Це також запобігає посиланням на одну таблицю користувачів за допомогою зовнішнього ключа.


1
Як я маю справу з тим, що, використовуючи спосіб 2, немає способу примусово встановити, що в двох інших таблицях є рівно один відповідний рядок?
Біллі ONeal

2
@Billy - Добре заперечення. Якщо ваші користувачі можуть мати лише одне або інше, ви можете це застосувати через ваш прошарок або тригери. Цікаво, чи існує спосіб на рівні DDL застосувати це обмеження. (На жаль, індексовані уявлення не дозволяють UNION , або я б запропонував индексированное вистава з унікальним індексом проти UNION ALLз uidз двох таблиць.)
Нік Хаммас

Звичайно, це передбачає, що RDBMS в першу чергу підтримував індексовані перегляди.
Біллі ONeal

1
Зручним способом реалізації подібного роду обмежень між таблицями було б включити атрибут розділення в таблицю супер-типу. Тоді кожен підтип може перевірити, чи він стосується лише супертипів, які мають відповідне значення атрибута розділення. Це економить необхідність провести тест на зіткнення, переглянувши одну або декілька інших таблиць підтипу.
Джоел Браун

1
@Joel - Так, наприклад, ми додаємо typeстовпчик до кожної таблиці підтипів, яка обмежена через CHECKобмеження, щоб мати саме одне значення (тип цієї таблиці). Потім ми робимо зовнішні ключі під таблиці до супер-таблиці в складові і на, uidі на type. Це геніально.
Нік Шамм

5

Їх би назвали

  1. Спадкування єдиної таблиці
  2. Наслідування таблиці класів
  3. Успадкування конкретного столу .

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

Наявність декількох таблиць змусить мене більше керувати даними для коду програми, але зменшить кількість невикористаного місця.


2
Існує додаткова методика під назвою "Спільний первинний ключ". У цій техніці таблиці підкласів не мають незалежно призначеного ідентифікатора первинного ключа. Натомість PK таблиць підкласу - це FK, що посилається на таблицю надкласу. Це надає кілька переваг, головним чином, це те, що вони застосовують характер взаємовідносин IS-A. Ця методика є доповненням до наслідування таблиці.
Вальтер Мітті
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.