Общий дизайн таблицы базы данных - PullRequest
9 голосов
/ 16 июня 2010

Просто пытаюсь найти лучший способ спроектировать мою таблицу для следующего сценария:

У меня есть несколько областей в моей системе (документы, проекты, группы и клиенты), и в каждой из них могут быть записаны комментариипротив них.

У меня вопрос, должна ли я иметь одну таблицу, подобную этой:

CommentID
DocumentID
ProjectID
GroupID
ClientID
etc

Где только один из идентификаторов будет иметь данные, а остальные будут NULL или я должен иметь отдельнуюТаблица CommentType и моя таблица комментариев выглядит следующим образом:

CommentID
CommentTypeID
ResourceID (this being the id of the project/doc/client)
etc

Я думаю, что вариант 2 будет более эффективным с точки зрения индексации.Это правильно?

Ответы [ 10 ]

5 голосов
/ 16 июня 2010

Читайте о нормализации базы данных.

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

4 голосов
/ 16 июня 2010

Вариант 2 - , а не - хорошее решение для реляционной базы данных.Он называется полиморфными ассоциациями (как упомянуто @Daniel Vassallo) и нарушает фундаментальное определение отношения.

Например, предположим, что у вас есть ResourceId 1234 в двух разных строках.Они представляют один и тот же ресурс?Это зависит от того, является ли CommentTypeId одинаковым в этих двух строках.Это нарушает концепцию типа в отношении.См. SQL и реляционную теорию от CJ Date для получения более подробной информации.

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

Я бы решил это с помощью решения, которое кратко упоминает @mdma (но потомигнорирует):

CREATE TABLE Commentable (
  ResourceId INT NOT NULL IDENTITY,
  ResourceType INT NOT NULL,
  PRIMARY KEY (ResourceId, ResourceType)
);

CREATE TABLE Documents (
  ResourceId INT NOT NULL,
  ResourceType INT NOT NULL CHECK (ResourceType = 1),
  FOREIGN KEY (ResourceId, ResourceType) REFERENCES Commentable
);

CREATE TABLE Projects (
  ResourceId INT NOT NULL,
  ResourceType INT NOT NULL CHECK (ResourceType = 2),
  FOREIGN KEY (ResourceId, ResourceType) REFERENCES Commentable
);

Теперь у каждого типа ресурса есть своя собственная таблица, но последовательный первичный ключ уникально выделяется Commentable.Заданное значение первичного ключа может использоваться только одним типом ресурса.

CREATE TABLE Comments (
  CommentId INT IDENTITY PRIMARY KEY,
  ResourceId INT NOT NULL,
  ResourceType INT NOT NULL,
  FOREIGN KEY (ResourceId, ResourceType) REFERENCES Commentable
);

Теперь Комментарии ссылаются на комментируемые ресурсы, с соблюдением ссылочной целостности.Данный комментарий может ссылаться только на один тип ресурса.Нет никакой вероятности аномалий или конфликтующих идентификаторов ресурсов.

Более подробно о полиморфных ассоциациях я расскажу в своей презентации Практические объектно-ориентированные модели в SQL и в моей книге Антипаттерны SQL .

3 голосов
/ 16 июня 2010

Чтобы продолжить с ответа @ OMG Ponies , то, что вы описываете во втором примере, называется Полиморфная ассоциация , где внешний ключ ResourceID может ссылаться на строки в более чемодин столОднако в базах данных SQL ограничение внешнего ключа может ссылаться только на одну таблицу.База данных не может принудительно применить внешний ключ в соответствии со значением в CommentTypeID.

Возможно, вас заинтересует следующий пост переполнения стека для одного решения этой проблемы:

3 голосов
/ 16 июня 2010

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

2 голосов
/ 16 июня 2010

Первый подход не велик, так как он довольно денормализован. Каждый раз, когда вы добавляете новый тип сущности, вам необходимо обновлять таблицу. Возможно, вам лучше сделать это атрибутом документа - т.е. сохранить комментарий в таблице документов.

Для подхода ResourceID для работы со ссылочной целостностью вам потребуется таблица Resource и внешний ключ ResourceID во всех ваших объектах Document, Project и т. Д. (Или использование таблицы сопоставления). .) Создание «ResourceID» мастером на все руки, который может быть documentID, projectID и т. Д., Не является хорошим решением, так как его нельзя использовать для разумного индексирования или ограничения внешнего ключа.

Для нормализации необходимо объединить таблицу комментариев в одну таблицу для каждого типа ресурса.

Comment
-------
CommentID
CommentText
...etc 

DocumentComment
---------------
DocumentID
CommentID

ProjectComment
--------------
ProjectID
CommentID

Если разрешен только один комментарий, то вы добавляете уникальное ограничение на внешний ключ для объекта (DocumentID, ProjectID и т. Д.). Это гарантирует, что для данного элемента может быть только одна строка и, следовательно, только один комментарий. Вы также можете убедиться, что комментарии не являются общими, используя уникальное ограничение для CommentID.

РЕДАКТИРОВАТЬ: Интересно, что это почти параллельно нормализованной реализации ResourceID - замените «Comment» в имени таблицы на «Resource» и измените «CommentID» на «ResourceID», и у вас есть структура, необходимая для связывания ResourceID с каждым ресурсом. Затем вы можете использовать одну таблицу "ResourceComment".

Если будут другие сущности, связанные с каким-либо типом ресурса (например, подробности аудита, права доступа и т. Д.), То использование таблиц сопоставления ресурсов - это путь, так как он позволит вам добавить нормализованные комментарии и любые другие связанные с ресурсом объекты.

1 голос
/ 16 июня 2010

Я бы не пошел ни с одним из этих решений. В зависимости от некоторых ваших требований вы можете использовать таблицу супертипа:

CREATE TABLE Commentable_Items (
    commentable_item_id    INT    NOT NULL,
    CONSTRAINT PK_Commentable_Items PRIMARY KEY CLUSTERED (commentable_item_id))
GO
CREATE TABLE Projects (
    commentable_item_id    INT    NOT NULL,
    ... (other project columns)
    CONSTRAINT PK_Projects PRIMARY KEY CLUSTERED (commentable_item_id))
GO
CREATE TABLE Documents (
    commentable_item_id    INT    NOT NULL,
    ... (other document columns)
    CONSTRAINT PK_Documents PRIMARY KEY CLUSTERED (commentable_item_id))
GO

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

Мне не очень нравится этот подход в вашем конкретном случае, потому что «иметь комментарии» недостаточно, чтобы собрать воедино такие вещи.

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

CREATE TABLE Comments (
    comment_id    INT            NOT NULL,
    comment_text  NVARCHAR(MAX)  NOT NULL,
    CONSTRAINT PK_Comments PRIMARY KEY CLUSTERED (comment_id))
GO
CREATE TABLE Document_Comments (
    document_id    INT    NOT NULL,
    comment_id     INT    NOT NULL,
    CONSTRAINT PK_Document_Comments PRIMARY KEY CLUSTERED (document_id, comment_id))
GO
CREATE TABLE Project_Comments (
    project_id     INT    NOT NULL,
    comment_id     INT    NOT NULL,
    CONSTRAINT PK_Project_Comments PRIMARY KEY CLUSTERED (project_id, comment_id))
GO

Если вы хотите ограничить комментарии одним документом (например), вы можете добавить уникальный индекс (или изменить первичный ключ) для comment_id в этой таблице ссылок.

Это все эти "маленькие" решения, которые будут влиять на конкретные ПК и ФК. Мне нравится этот подход, потому что в каждой таблице ясно, что это такое. В базах данных это обычно лучше, чем иметь «общие» таблицы / решения.

0 голосов
/ 17 июня 2010

Приложение ломбарда:

У меня есть отдельные таблицы для транзакций по кредитам, покупкам, запасам и продажам. Строки каждой таблицы объединяются с соответствующими строками клиента с помощью:

customer.pk [serial] = loan.fk [integer];
                     = purchase.fk [integer];
                     = inventory.fk [integer];
                     = sale.fk [integer]; 

Я объединил четыре таблицы в одну таблицу под названием «транзакция», где столбец:

action.trx_type char (1) {L = ссуда, P = покупка, I = запас, S = продажа}

Сценарий:

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

Я разработал общую таблицу транзакций, где, например:

action.main_amount DECIMAL (7,2)

в кредитной сделке удерживается сумма залога, при покупке держит цену покупки, В инвентаре и продаже выставлена ​​цена продажи.

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

0 голосов
/ 16 июня 2010

Если вы несете одинаковые данные обо всех комментариях, независимо от того, к чему они относятся, я бы проголосовал против создания нескольких таблиц комментариев. Может быть, комментарий - это просто «о чем идет речь» и текст, но если у вас сейчас нет других данных, скорее всего, вы будете: дата ввода комментария, идентификатор пользователя, который его сделал, и т. Д. С несколькими таблицами вы необходимо повторить все эти определения столбцов для каждой таблицы.

Как уже отмечалось, использование одного ссылочного поля означает, что вы не можете наложить на него ограничение внешнего ключа. Это очень плохо, но это ничего не нарушает, это просто означает, что вы должны выполнить проверку с помощью триггера или в коде. А если серьезно, то соединения становятся сложными. Вы можете просто сказать «из комментария присоединиться к документу с помощью (documentmentid)». Вам необходимо сложное соединение, основанное на значении поля типа.

Так что, несмотря на то, что несколько полей указателей уродливы, я склонен думать, что это правильный путь. Я знаю, что некоторые люди из БД говорят, что в таблице никогда не должно быть пустого поля, что вы всегда должны разбивать его на другую таблицу, чтобы этого не происходило, но я не вижу реального преимущества в следовании этому правилу.

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

0 голосов
/ 16 июня 2010

Вариант 2 - хороший путь. Проблема, с которой я сталкиваюсь, заключается в том, что вы кладете ключ ресурсов на эту таблицу. Каждый из идентификаторов из разных ресурсов может быть продублирован. Когда вы присоединяете ресурсы к комментариям, вы, скорее всего, получите комментарии, которые не относятся к этому конкретному ресурсу. Это будет считаться присоединением многих ко многим. Я думаю, что лучшим вариантом будет иметь ваши таблицы ресурсов, таблицу комментариев, а затем таблицы, которые перекрестно ссылаются на тип ресурса и таблицу комментариев.

0 голосов
/ 16 июня 2010

Из вариантов, которые вы даете, я бы пошел на номер 2.

...