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


11

У мене виникають певні проблеми з відтворенням бази даних із співвідношенням один до багатьох (1: M) між користувачами та предметами .

Це досить просто, так; однак кожен предмет належить до певної категорії (наприклад, автомобіль , човен або літак ), і кожна категорія має певну кількість атрибутів, наприклад:

Car структура:

+----+--------------+--------------+
| PK | Attribute #1 | Attribute #2 |
+----+--------------+--------------+

Boat структура:

+----+--------------+--------------+--------------+
| PK | Attribute #1 | Attribute #2 | Attribute #3 |
+----+--------------+--------------+--------------+

Plane структура:

+----+--------------+--------------+--------------+--------------+
| PK | Attribute #1 | Attribute #2 | Attribute #3 | Attribute #4 |
+----+--------------+--------------+--------------+--------------+

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

Хоча спочатку це виглядало чудово, я не міг знайти спосіб створити взаємозв’язок між Елементами та Категоріями через базу даних, оскільки, принаймні, з мого скромного досвіду роботи адміністратора бази даних, під час створення іноземних ключів я чітко повідомляю базу даних назва таблиці та стовпець.

Зрештою, я хотів би тверду структуру , щоб зберігати всі дані, в той час як такі, що всі кошти , щоб перерахувати всі атрибути всіх Items Користувач може мати з одним запитом.

Я міг би жорстко динамічні запити з серверним мовою, але я вважаю , що це неправильно і не дуже оптимальними.

Додаткова інформація

Це мої відповіді на коментарі MDCCL:

1. Скільки цікавих категорій предметів є у вашому бізнесі, три (тобто автомобілі , катери та літаки ) чи більше?

Насправді це дуже просто: Всього існує п'ять категорій .

2. Чи завжди той самий Елемент належить одному і тому ж Користувачеві (тобто, коли даний Елемент буде "призначений" певному Користувачеві, його неможливо змінити)?

Ні, вони могли змінитися. У вигаданому сценарії питання, це було б так, як Користувач A продає Пункт №1 для Користувача B , тому право власності повинно бути відображено.

3. Чи є атрибути, якими поділяються деякі або всі Категорії ?

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

4. Чи є ймовірність того, що кардинальність відносин між Користувачем і Пунктом є багато-багато-багато (M: N) замість одного-до-багатьох (1: M)? Наприклад, у випадку наступних правил ведення бізнесу: A User owns zero-one-or-many ItemsіAn Item is owned by one-to-many Users

Ні, тому що Елементи описували б фізичний об'єкт. Користувачі матимуть віртуальну копію, кожна з яких буде ідентифікована унікальним GUID v4

5. Щодо вашої наступної відповіді на один із коментарів до запитання:

"У вигаданому сценарії запитання це було б так, як Користувач A продає Пункт №1 для Користувача B , тому право власності має бути відображено."

Схоже, ви плануєте відстежувати еволюцію власності на предмет , так би мовити. Таким чином, які атрибути ви б хотіли зберегти про таке явище? Тільки модифікація атрибута, яка вказує конкретного Користувача, який є власником конкретного предмета ?

Ні, не дуже. Право власності може змінитися, але мені не потрібно слідкувати за попереднім власником .

Відповіді:


18

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

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

Правила бізнесу

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

  • Користувач має нульовий один або багато- товари
  • Пункт належить точно-один користувач в певний момент часу
  • Один предмет може бути власником одного для багатьох користувачів у різні моменти часу
  • Пункт класифікується рівно однієї категорії
  • Пункт є, в усі часи,
    • або Автомобіль
    • або човен
    • або Літак

Ілюстративна діаграма IDEF1X

На малюнку 1 представлена діаграма IDEF1X 1, яку я створив, щоб згрупувати попередні рецептури разом з іншими правилами бізнесу, які видаються відповідними:

Рисунок 1 - Структура супертипу і підтипу предметів та категорій

Супертип

З одного боку, Item , супертип, представляє властивості або атрибути, спільні для всіх Категорій , тобто

  • CategoryCode - визначений як ІНОЗЕМНИЙ КЛЮЧ (FK), що посилається на Category.CategoryCode і функціонує як дискримінатор підтипу , тобто вказує точну категорію підтипу, з якою повинен бути пов'язаний даний елемент -,
  • OwnerId - відрізняється як FK, який вказує на User.UserId , але я призначив йому ім'я 2 ролі , щоб точніше відобразити його особливі наслідки -,
  • Foo ,
  • Бар ,
  • Баз і
  • CreatedDateTime .

Підтипи

З іншого боку, властивості ‡, що стосуються кожної конкретної категорії , тобто

  • Qux і Corge ;
  • Граул , Гарплі і Плуг ;
  • Xyzzy , Thud , Wibble та Flob ;

відображаються у відповідному полі підтипу.

Ідентифікатори

Потім, Item.ItemId PRIMARY KEY (PK) перемістив 3 до підтипів з різними іменами ролей, тобто,

  • CarId ,
  • BoatId і
  • PlaneId .

Взаємовиключні асоціації

Як зображено, існує зв'язок або зв'язок кардинальності один на один (1: 1) між (a) кожним появою супертипу та (b) його додатковим підтипом.

Ексклюзивний підтип символ зображує той факт , що підтипи є взаємовиключними, тобто конкретний Item явище може бути доповнений тільки одним екземпляром підтипу: або один автомобіль , або один Plane , або одна човен (ніколи на два або більше).

, Я використав класичні назви заповнювачів, щоб визначити деякі властивості типу сутності, оскільки їхні фактичні номінали не були вказані у питанні.

Макет рівня логічного рівня експозитарію

Отже, для обговорення логічного дизайну експозиторії я вивів наступні оператори SQL-DDL на основі діаграми IDEF1X, відображеної та описаної вище:

-- You should determine which are the most fitting 
-- data types and sizes for all your table columns 
-- depending on your business context characteristics.

-- Also, you should make accurate tests to define the 
-- most convenient INDEX strategies based on the exact 
-- data manipulation tendencies of your business context.

-- As one would expect, you are free to utilize 
-- your preferred (or required) naming conventions. 

CREATE TABLE UserProfile (
    UserId          INT      NOT NULL,
    FirstName       CHAR(30) NOT NULL,
    LastName        CHAR(30) NOT NULL,
    BirthDate       DATE     NOT NULL,
    GenderCode      CHAR(3)  NOT NULL,
    Username        CHAR(20) NOT NULL,
    CreatedDateTime DATETIME NOT NULL,
    --
    CONSTRAINT UserProfile_PK  PRIMARY KEY (UserId),
    CONSTRAINT UserProfile_AK1 UNIQUE ( -- Composite ALTERNATE KEY.
        FirstName,
        LastName,
        GenderCode,
        BirthDate
    ),
    CONSTRAINT UserProfile_AK2 UNIQUE (Username) -- ALTERNATE KEY.
);

CREATE TABLE Category (
    CategoryCode     CHAR(1)  NOT NULL, -- Meant to contain meaningful, short and stable values, e.g.; 'C' for 'Car'; 'B' for 'Boat'; 'P' for 'Plane'.
    Name             CHAR(30) NOT NULL,
    --
    CONSTRAINT Category_PK PRIMARY KEY (CategoryCode),
    CONSTRAINT Category_AK UNIQUE      (Name) -- ALTERNATE KEY.
);

CREATE TABLE Item ( -- Stands for the supertype.
    ItemId           INT      NOT NULL,
    OwnerId          INT      NOT NULL,
    CategoryCode     CHAR(1)  NOT NULL, -- Denotes the subtype discriminator.
    Foo              CHAR(30) NOT NULL,
    Bar              CHAR(30) NOT NULL,
    Baz              CHAR(30) NOT NULL,  
    CreatedDateTime  DATETIME NOT NULL,
    --
    CONSTRAINT Item_PK             PRIMARY KEY (ItemId),
    CONSTRAINT Item_to_Category_FK FOREIGN KEY (CategoryCode)
        REFERENCES Category    (CategoryCode),
    CONSTRAINT Item_to_User_FK     FOREIGN KEY (OwnerId)
        REFERENCES UserProfile (UserId)  
);

CREATE TABLE Car ( -- Represents one of the subtypes.
    CarId INT      NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
    Qux   CHAR(30) NOT NULL,
    Corge CHAR(30) NOT NULL,   
    --
    CONSTRAINT Car_PK         PRIMARY KEY (CarId),
    CONSTRAINT Car_to_Item_FK FOREIGN KEY (CarId)
        REFERENCES Item (ItemId)  
);

CREATE TABLE Boat ( -- Stands for one of the subtypes.
    BoatId INT      NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
    Grault CHAR(30) NOT NULL,
    Garply CHAR(30) NOT NULL,   
    Plugh  CHAR(30) NOT NULL, 
    --
    CONSTRAINT Boat_PK         PRIMARY KEY (BoatId),
    CONSTRAINT Boat_to_Item_FK FOREIGN KEY (BoatId)
        REFERENCES Item (ItemId)  
);

CREATE TABLE Plane ( -- Denotes one of the subtypes.
    PlaneId INT      NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
    Xyzzy   CHAR(30) NOT NULL,
    Thud    CHAR(30) NOT NULL,  
    Wibble  CHAR(30) NOT NULL,
    Flob    CHAR(30) NOT NULL,  
    --
    CONSTRAINT Plane_PK         PRIMARY KEY (PlaneId),
    CONSTRAINT Plane_to_Item_PK FOREIGN KEY (PlaneId)
        REFERENCES Item (ItemId)  
);

Як було показано, тип похідності та кожен з типів суб'єктів представлені відповідною базовою таблицею.

Стовпці CarId, BoatIdі PlaneId, стримуються як ПКС відповідних таблиць, допомога в поданні концептуального рівня один-до-одного асоціації шляхом обмеження FK § , що вказує на ItemIdколонку, яка туги як PK в Itemтаблиці. Це означає, що у фактичній «парі» і рядки супертипу, і рядки підтипу ідентифікуються тим самим значенням PK; таким чином, згадати це більш ніж доречно

  • (А) прикріплення додаткової колонки , щоб тримати під контролем система-сурогатних значення до (б) таблиць , які стоять для підтипів (с) повністю зайвим .

§ Для запобігання проблем та помилок, що стосуються (особливо ЗОВНІШНЬОГО) визначень обмежень КЛЮЧ - ситуації, про які ви згадували в коментарях, дуже важливо враховувати залежність існування, що має місце серед різних таблиць, як це пояснюється в порядок декларування таблиць у структурі DDL експозиторії, який я також надав у цьому SQL Fiddle .

Наприклад, додавши додатковий стовпець із властивістю AUTO_INCREMENT до таблиці бази даних, побудованої на MySQL.

Цілісність та послідовність

Важливо зазначити, що у вашому бізнес-середовищі ви повинні (1) забезпечити, щоб кожен рядок "супертипу" постійно доповнювався відповідним аналогом "підтипу", і, у свою чергу, (2) гарантував, що зазначене Рядок "підтип" сумісний зі значенням, що міститься в стовпці "дискримінатор" рядка "супертип".

Наскільки було б дуже елегантно застосовувати такі обставини декларативно , але, на жаль, жодна з основних платформ SQL не забезпечила належних механізмів для цього, наскільки я знаю. Тому вдаючись до процесуального кодексу в рамках КИСЛОТНИХ ПЕРЕВАГІВ, це досить зручно, тому що ці умови завжди виконуються у вашій базі даних. Іншим варіантом може бути використання TRIGGERS, але вони, як правило, роблять речі неохайними.

Оголошення корисних поглядів

Маючи логічний дизайн, як описаний вище, було б дуже практично створити один або кілька представлень, тобто похідних таблиць, що містять стовпці, що належать до двох або більше відповідних базових таблиць. Таким чином, ви можете, наприклад, ВИБІРАТИ безпосередньо З цих поглядів, не записуючи всі ПРИЄДНАННЯ щоразу, коли вам потрібно отримати "комбіновану" інформацію.

Зразок даних

У цьому відношенні скажемо, що базові таблиці "заповнені" типовими даними, наведеними нижче:

--

INSERT INTO UserProfile 
    (UserId, FirstName, LastName, BirthDate, GenderCode, Username, CreatedDateTime)
VALUES
    (1, 'Edgar', 'Codd', '1923-08-19', 'M', 'ted.codd', CURDATE()),
    (2, 'Michelangelo', 'Buonarroti', '1475-03-06', 'M', 'michelangelo', CURDATE()),
    (3, 'Diego', 'Velázquez', '1599-06-06', 'M', 'd.velazquez', CURDATE());

INSERT INTO Category 
    (CategoryCode, Name)
VALUES
    ('C', 'Car'), ('B', 'Boat'), ('P', 'Plane');

-- 1. ‘Full’ Car INSERTion

-- 1.1 
INSERT INTO Item
    (ItemId, OwnerId, CategoryCode, Foo, Bar, Baz, CreatedDateTime)
VALUES
    (1, 1, 'C', 'This datum', 'That datum', 'Other datum', CURDATE());

 -- 1.2
INSERT INTO Car
    (CarId, Qux, Corge)
VALUES
    (1, 'Fantastic Car', 'Powerful engine pre-update!');

-- 2. ‘Full’ Boat INSERTion

-- 2.1
INSERT INTO Item
  (ItemId, OwnerId, CategoryCode, Foo, Bar, Baz, CreatedDateTime)
VALUES
  (2, 2, 'B', 'This datum', 'That datum', 'Other datum', CURDATE());

-- 2.2
INSERT INTO Boat
    (BoatId, Grault, Garply, Plugh)
VALUES
    (2, 'Excellent boat', 'Use it to sail', 'Everyday!');

-- 3 ‘Full’ Plane INSERTion

-- 3.1
INSERT INTO Item
  (ItemId, OwnerId, CategoryCode, Foo, Bar, Baz, CreatedDateTime)
VALUES
  (3, 3, 'P', 'This datum', 'That datum', 'Other datum', CURDATE());

-- 3.2
INSERT INTO Plane
    (PlaneId, Xyzzy, Thud, Wibble, Flob)
VALUES
    (3, 'Extraordinary plane', 'Traverses the sky', 'Free', 'Like a bird!');

--

Потім, вигідне думка , яке збирає один з стовпців Item, Carі UserProfile:

--

CREATE VIEW CarAndOwner AS
    SELECT C.CarId,
           I.Foo,
           I.Bar,
           I.Baz,
           C.Qux,
           C.Corge,           
           U.FirstName AS OwnerFirstName,
           U.LastName  AS OwnerLastName
        FROM Item I
        JOIN Car C
          ON C.CarId = I.ItemId
        JOIN UserProfile U
          ON U.UserId = I.OwnerId;

--

Звичайно, можна дотримуватися аналогічного підходу, щоб ви могли також ВИБІРАТИ «повну» Boatта Planeінформацію прямо з однієї єдиної таблиці (похідної в цих випадках).

Після цього -Якщо ви не заперечуєте про наявність NULL знаків в результаті sets- з наступним визначенням VIEW, ви можете, наприклад, «збирати» стовпці з таблиць Item, Car, Boat, Planeі UserProfile:

--

CREATE VIEW FullItemAndOwner AS
    SELECT I.ItemId,
           I.Foo, -- Common to all Categories.
           I.Bar, -- Common to all Categories.
           I.Baz, -- Common to all Categories.
          IC.Name      AS Category,
           C.Qux,    -- Applies to Cars only.
           C.Corge,  -- Applies to Cars only.
           --
           B.Grault, -- Applies to Boats only.
           B.Garply, -- Applies to Boats only.
           B.Plugh,  -- Applies to Boats only.
           --
           P.Xyzzy,  -- Applies to Planes only.
           P.Thud,   -- Applies to Planes only.
           P.Wibble, -- Applies to Planes only.
           P.Flob,   -- Applies to Planes only.
           U.FirstName AS OwnerFirstName,
           U.LastName  AS OwnerLastName
        FROM Item I
        JOIN Category IC
          ON I.CategoryCode = IC.CategoryCode
   LEFT JOIN Car C
          ON C.CarId = I.ItemId
   LEFT JOIN Boat B
          ON B.BoatId = I.ItemId
   LEFT JOIN Plane P
          ON P.PlaneId = I.ItemId               
        JOIN UserProfile U
          ON U.UserId = I.OwnerId;

--

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

Зразкові дані та всі визначення перегляду включені в цю скрипку SQL, щоб їх можна було спостерігати «в дії».

Маніпулювання даними: Код програми та псевдоніми колон та програм

Використання прикладних програм (кодів) програми (якщо саме так ви маєте на увазі "специфічний код на стороні сервера") та псевдоніми стовпців - це інші важливі моменти, які ви зібрали в наступних коментарях:

  • Мені вдалося вирішити проблему [JOIN] із специфічним кодом на сервері, але я дійсно не хочу цього робити, і додавання псевдонімів до всіх стовпців може бути "наголос".

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

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

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

Подібні сценарії

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

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


Кінцеві замітки

1 Визначення інтеграції для інформаційного моделювання ( IDEF1X ) - це дуже рекомендована методика моделювання даних, яка була встановлена ​​як стандарт в грудні 1993 р. Національним інститутом стандартів і технологій США (NIST). Воно міцно засноване на (а) деякі з теоретичних робітавтором якого є єдиним оригинатора в реляційної моделі , тобто д - р Ф. Кодда ; (b) погляд на відносини між сутністю , розроблений доктором П.П. Ченом ; а також (c) техніку проектування логічної бази даних, створену Робертом Г. Брауном.

2 У IDEF1X назва ролі - це відмітна мітка, присвоєна властивості (або атрибуту) ФК, щоб виразити значення, яке вона містить у межах відповідного типу сутності.

3 Стандарт IDEF1X визначає міграцію ключів як "Процес моделювання розміщення первинного ключа батьківського або родового об'єкта в його дочірній або категорійній сутності як іноземний ключ".


1
Я не впевнений, що я розумію ваш запит, але як показано в макеті DDL, Itemтаблиця містить CategoryCodeстовпчик. Як згадувалося в розділі «Міркування про цілісність та послідовність»:
MDCCL

1
Важливо зазначити, що у вашому бізнес-середовищі ви повинні (1) забезпечити, щоб кожен рядок "супертипу" постійно доповнювався відповідним аналогом "підтипу", і, у свою чергу, (2) гарантував, що зазначене Рядок "підтип" сумісний зі значенням, що міститься в стовпці "дискримінатор" рядка "супертип".
MDCCL

1
Наскільки було б дуже елегантно застосовувати такі обставини декларативно, але, на жаль, жодна з основних платформ SQL не забезпечила належних механізмів для цього, наскільки я знаю. Тому вдаючись до процесуального кодексу в рамках КИСЛОТНИХ ПЕРЕВАГІВ, це досить зручно, щоб ці умови завжди виконувалися у вашій базі даних. Іншим варіантом може бути використання TRIGGERS, але вони, як правило, роблять речі неохайними.
MDCCL

1
Суть у тому, що жодна реалізація SQL (включаючи діалект MySQL) не забезпечує належної підтримки ВПРОВАДЖЕННЯ, потужних та елегантних декларативних інструментів, які допомогли б уникнути звернення до процедурних підходів (TRANSACTIONS або TRIGGERS), або не працювали надмірно, як наприклад , наприклад, непотрібне повторення CategoryColumnтаблиць, що стоять за підтипами (з усіма наслідками на логічному [наприклад, аномалії модифікації] та фізичному рівнях абстрагування (наприклад, додаткові індекси, більші структури тощо).
MDCCL

2
Поки будь-який з постачальників / розробників системи управління базами даних не надасть ПОПЕРЕДЖЕННЯ - належний інструмент для виконання цього завдання - я вважаю за краще (а) процедурні підходи - будь то ПЕРЕВІРКИ або ТРІГЕРИ - над (б) надмірним ходом дій, хоча (б) - це можливість, яку я особисто не рекомендую. Звичайно, DBA має ретельно керувати дозволами щодо дійсних операцій по маніпулюванню даними, які можуть бути виконані в базі даних, що стосуються релевантності, що, безумовно, дуже допомагає у підтримці цілісності даних.
MDCCL

0

Давайте називаємо основні таблиці Продукти. Тут розміщуються спільні атрибути. Тоді скажімо, у нас є автомобільний стіл, стіл літака та стіл човна. У цих трьох таблицях буде ключ ProductID із обмеженням FK в ідентифікаційному рядку таблиці Product. Якщо ви хочете їх усіх - приєднуйтесь до них. Якщо ви хочете лише автомобілі, ліворуч приєднуйтесь до Автомобілів із продуктами (або право приєднуйтесь до продуктів та автомобілів, але я вважаю за краще завжди використовувати лівосторонні з'єднання).

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


А потім я приєднуюся до користувачів із продуктами?
користувач5613506

1
Зазвичай вам не потрібна інформація про користувачів, коли ви повертаєте список товарів на передній план, вам потрібна інформація про продукт. Немає сенсу приєднуватися до Користувачів і Продуктів і повертати однакову інформацію про користувачів для кожного повернутого рядка продукту. Тож спочатку ви фільтруєте за типом продукту, приєднуючись до таблиці продуктів та відповідної підтаблиці (Автомобіль, човен ...), а потім фільтруєте за користувачем, використовуючи пункт WHERE. Як правило, ви хочете мати власник ID в таблиці продуктів (FK у стовпці ідентифікатора таблиці користувача). Таким чином, ви додасте WHERE власника = [Request.User].
neManiac
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.