Следует ли использовать наследование одной таблицы или нескольких таблиц, объединенных в представление? - PullRequest
3 голосов
/ 29 июля 2010

Допустим, у вас есть таблица заметок.Примечание может быть о конкретном счете, строке заказа или заказе.

  • Примечания, касающиеся учетной записи, не относятся к какой-либо конкретной строке заказа или заказу.
  • Примечания, относящиеся к строке заказа, также применяются к родительскому заказу и учетной записи, которая прикреплена кзаказ.
  • Примечания, относящиеся к заказу, также применяются к прикрепленной учетной записи, но не к строке заказа.

Таблица NOTES

[Id]          [int] IDENTITY(1,1) NOT NULL
[NoteTypeId]  [smallint] NOT NULL
[AccountId]   [int] NULL
[OrderId]     [int] NULL
[OrderLineId] [int] NULL,
[Note]        [varchar](300) NOT NULL

Идея состоит в том, чтоесли я просматриваю клиента, я вижу все заметки, которые так или иначе связаны между собой.Сначала я создал таблицу заметок для каждого из вышеперечисленных и объединил их в представление.Проблема здесь заключается в редактировании / удалении записи.Примечания могут быть отредактированы / удалены на конкретном элементе или в общем представлении примечаний учетной записи или заказа.Этот метод усложнил этот процесс.

Затем я переключился на шаблон Наследование отдельных таблиц .Моя таблица заметок имеет значения NULL для AccountId, OrderId и OrderLineId.Я также добавил NoteTypeId для явной идентификации записи.Намного проще управлять сценариями обновления / удаления.

У меня есть некоторые проблемы и вопросы, связанные с этим подходом.

  • Целостность - Хотя сложные ограничения могут быть установлены в SQL и / или в коде, большинству администраторов баз данных не понравился бы подход STI.
  • Обсуждается идея группы нулей (хотя я считаю, что производительность в SQL 2008 улучшилась благодаря хранению нулевых значений)
  • Таблица в СУБД не должна представлять объектв коде.Нормализация в таблице не говорит о том, что таблица должна быть уникальным объектом.Я верю, что два предыдущих предложения были правдой, что вы скажете?

Некоторые обсуждали здесь. Является ли чрезмерное использование обнуляемых столбцов в базе данных "запахом кода"? Я должен был бы сказать, что согласен с Ианом, но мне также хотелось бы иметь и несколько противоположных взглядов.

Ответы [ 3 ]

2 голосов
/ 29 июля 2010

Хотя сложные ограничения могут быть установлены в SQL и / или в коде, большинству администраторов баз данных не понравился бы подход STI.

Поскольку вам нужна дополнительная логика (ограничение CHECK или триггер) для реализации бизнес-правила, согласно которому примечание относится только к одной из сущностей - счет, заказ, строка заказа.

Более масштабируемо реализовать таблицу «многие ко многим» между каждой сущностью и таблицей заметок.

  • Нет необходимости в операторе ALTER TABLE для добавления еще одного обнуляемого внешнего ключа (существует предел столбца, но большинство из них не может его достичь)
  • Запись одной заметкиможет быть связан с несколькими сущностями
  • Не влияет на существующие записи, если добавляется новая сущность и таблица «многие ко многим»
0 голосов
/ 30 июля 2010

Изначально я [создал] отдельные заметки таблица для каждого из вышеперечисленных и объединяю их в представлении.

Это заставляет меня задуматься, рассматривали ли вы возможность использования многостоловой структуры без столбцов NULLable, где каждая заметка получает уникальный идентификатор независимо от типа. Вы можете представить данные в «наследовании одной таблицы» (или аналогично) в запросе, не используя UNION.

Ниже предлагается структура. Я изменил NoteTypeId на VARCHAR, чтобы сделать разные типы более понятными и удобными для чтения (вы все равно не перечислили значения INTEGER):

CREATE TABLE Notes
(
 Id INTEGER IDENTITY(1,1) NOT NULL UNIQUE, 
 NoteType VARCHAR(11) NOT NULL
    CHECK (NoteType IN ('Account', 'Order', 'Order line')), 
 Note VARCHAR(300) NOT NULL, 
 UNIQUE (Id, NoteType)
);

CREATE TABLE AccountNotes
(
 Id INTEGER NOT NULL UNIQUE, 
 NoteType VARCHAR(11) 
    DEFAULT 'Account' 
    NOT NULL
    CHECK (NoteType = 'account'),
 FOREIGN KEY (Id, NoteType)
    REFERENCES Notes (Id, NoteType)
       ON DELETE CASCADE, 
 AccountId INTEGER NOT NULL
    REFERENCES Accounts (AccountId)
);

CREATE TABLE OrderNotes
(
 Id INTEGER NOT NULL UNIQUE, 
 NoteType VARCHAR(11) 
    DEFAULT 'Order'
    NOT NULL
    CHECK (NoteType = 'Order'),
 FOREIGN KEY (Id, NoteType)
    REFERENCES Notes (Id, NoteType)
       ON DELETE CASCADE, 
 OrderId INTEGER NOT NULL
    REFERENCES Orders (OrderId)
);

CREATE TABLE OrderLineNotes
(
 Id INTEGER NOT NULL UNIQUE, 
 NoteType VARCHAR(11) 
    DEFAULT 'Order line'
    NOT NULL
    CHECK (NoteType = 'Order line'),
 FOREIGN KEY (Id, NoteType)
    REFERENCES Notes (Id, NoteType)
       ON DELETE CASCADE, 
 OrderLineId INTEGER NOT NULL
    REFERENCES OrderLines (OrderLineId)
);

Чтобы представить данные в структуре «наследования одной таблицы» (т. Е. Все JOIN с и без UNION с):

SELECT N1.Id, N1.NoteType, N1.Note, 
       AN1.AccountId, 
       ON1.OrderId, 
       OLN1.OrderLineId
  FROM Notes AS N1
       LEFT OUTER JOIN AccountNotes AS AN1
          ON N1.Id = AN1.Id
       LEFT OUTER JOIN OrderNotes AS ON1
          ON N1.Id = ON1.Id
       LEFT OUTER JOIN OrderLineNotes AS OLN1
          ON N1.Id = OLN1.Id;

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

CHECK (
       (
        AccountId IS NOT NULL
        AND OrderId IS NULL
        AND OrderLineId IS NULL
       )
       OR
       (
        AccountId IS NULL
        AND OrderId IS NOT NULL
        AND OrderLineId IS NULL
       )
       OR
       (
        AccountId IS NULL
        AND OrderId IS NULL
        AND OrderLineId IS NOT NULL
       )
      );

CHECK (
       (
        NoteType = 'Account'
        AND AccountId IS NOT NULL
       )
       OR
       (
        NoteType = 'Order'
        AND OrderId IS NOT NULL
       )
       OR 
       (
        NoteType = 'Order line'
        AND OrdereLineId IS NOT NULL
       )
      );

etc etc

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

0 голосов
/ 30 июля 2010

Кажется, STI будет работать нормально в вашем случае ?.Если я правильно прочитал ваши требования, наследование сущности было бы цепочкой:

Примечание <- AccountNote (AccountId) <- AccountAndOrderNote (OrderId) <-AccountAndOrderAndOrderLineNote (OrderLineId) </p>

Целостность: Конечно, нетпроблема?Каждый из AccountId, OrderId и OrderLineId может быть FK'd для их соответствующих таблиц (или быть NULL). Если, с другой стороны, если вы удалили AccountId, OrderId и OrderLineId (я НЕ рекомендую BTW!), А вместо этого просто ObjectId и NoteTypeId, тогда вы не могли бы добавить RI и имели бы действительно грязный тип CASE WHEN Join.

Производительность: поскольку вы говорите, что AccountId должен присутствовать всегда, я думаю, он может быть ненулевым, а так как OrderLine не можетсуществуют без Order, индекс (AccountId, OrderId) или (AccountId, OrderId, OrderLineId), кажется, имеет смысл (в зависимости от компромиссов выбора по сравнению с узостью в среднем #OrderLines на заказ)

Но OMG Ponies право беспорядочных ALTER TABLE, чтобы распространить это на новые типы заметок, и индексация вызовет головную боль, если новые заметки не являются производными от учетной записи.

HTH

...