Дизайн базы данных для маркировки нескольких типов сущностей

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

Я думал о создании одной таблицы с уникальным идентификатором, на который ссылаются все сущности, и соотношением n: m между таблицей тегов и таблицей «уникальный идентификатор». Таким образом, существует только одна перекрестная таблица между таблицей «уникальных идентификаторов» и таблицей тегов.

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


person Saab    schedule 19.11.2008    source источник


Ответы (6)


Я бы сказал, это зависит от того, как вы хотите использовать теги.

Я бы предположил, что вы могли бы создать дополнительную таблицу пересечений для каждого типа объекта, который хотите пометить, если вы ищите только один тип объекта за раз. Другими словами, было бы нормально сказать: «Покажи мне ингредиенты с тегом 'yummy'», но не совсем понятно, что это означало бы сказать: «Покажи мне ингредиенты и издателей рецептов с тегом 'yummy'». В этом случае можно иметь отдельную таблицу пересечений для каждого объекта.

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

CREATE TABLE Recipes (
  recipe_id INT NOT NULL PRIMARY KEY, -- not auto-generated
  FOREIGN KEY (recipe_id) REFERENCES Taggables(id)
);

Единственная слабость этого плана заключается в том, что вы не можете запретить строке в Recipes и Ingredients указывать на одну и ту же строку в Taggables.

INSERT INTO Taggables (id) VALUES (327);
INSERT INTO Recipes (recipe_id, name) VALUES (327, 'Hollandaise sauce');
INSERT INTO Ingredients (ingr_id, name) VALUES (327, 'eggs');

Вы хотите, чтобы каждый тег, связанный с яйцами, также относился к голландскому соусу?

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

person Bill Karwin    schedule 19.11.2008
comment
Хм ... Я думал, что два объекта (например, два рецепта; или рецепт и ингредиент) могут иметь общий тег, но не могут быть одним и тем же экземпляром taggable. Как вы думаете, имеет ли какой-либо практический смысл создание тегов (не тегов), которыми могут пользоваться объекты? - person Yarik; 20.11.2008
comment
Верно, это моя точка зрения; Я бы ожидал, что несколько сущностей будут ссылаться на одну и ту же строку в taggables редко или вообще не имеют смысла. В идеале ограничения БД предотвращают недопустимые записи, но в этом дизайне они не могут. - person Bill Karwin; 20.11.2008

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

Однако одна важная деталь в вашем дизайне остается для меня неоднозначной: если у вас будет что-то в этом роде

- - - - - - - - - -
Tag
    ID           // PK
    Name
    ...

- - - - - - - - - -
Taggable
    ID           // PK
    ...

- - - - - - - - - -
TagAssignment
    Tag_ID       // FK -> Tag.ID
    Taggable_ID  // FK -> Taggable.ID
    ...

- - - - - - - - - -
EntityOne
    Taggable_ID  // FK -> Taggable.ID
    ...

- - - - - - - - - -
EntityTwo
    Taggable_ID  // FK -> Taggable.ID
    ...

тогда будут ли у ваших классов сущностей свои собственные первичные ключи или вы собираетесь использовать EntityOne.TaggableID и EntityTwo.TaggableID в качестве первичных ключей де-факто для EntityOne и EntityTwo?

В большинстве случаев я был бы осторожен и позволил бы сущностям иметь свои собственные идентификаторы:

- - - - - - - - - -
EntityOne
    ID           // PK
    Taggable_ID  // FK -> Taggable.ID (Nullable)
    ...

- - - - - - - - - -
EntityTwo
    ID           // PK
    Taggable_ID  // FK -> Taggable.ID (Nullable)
    ...

Это не потребовало бы, чтобы каждая сущность имела соответствующий экземпляр Taggable, и, следовательно, это не потребовало бы, чтобы каждый фрагмент кода, связанный с сущностью, также знал о тегах. Однако, если тегирование будет действительно повсеместным в системе, и если вы уверены, что вам не понадобятся никакие другие «общие предки» для сущностей (то есть кроме Taggable), тогда вы можете обойтись без «внутренних» «Идентификаторы сущностей.

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


В ответ на комментарий Билла Карвина:

Вы правы: описанный выше дизайн не мешает нескольким объектам ссылаться на один и тот же Taggable. Но:

  1. Как я уже сказал, все зависит от требований. Если мы уверены, что Taggable будет единственным «общим предком» сущностей, тогда можно использовать Taggable_ID FK в качестве PK для сущностей. Но, например, что, если некоторые объекты, которые могут быть "помечены", также должны быть "наблюдаемыми" (например, уведомления, расписания уведомлений и т. Д.) Или "что угодно" :-)? Можем ли мы отключить все эти «способности», жестко привязав любую сущность к Taggable?

  2. Если вы действительно хотите, чтобы на уровне БД соблюдалось ограничение one-taggable-one-entity ... AFAIK, есть по крайней мере один общий способ сделать это, не заставляя FK выступать в качестве PK: путем введения "типов" тегов (которые в любом случае может быть полезен для некоторых других функций).

Что-то в этом роде позволит нам съесть торт и съесть его:

- - - - - - - - - -
Taggable
    ID           // PK
    Type        
    ... 
    - - - - - - - -
    Constraint: (ID, Type) is unique


- - - - - - - - - -
EntityOne
    ID
    Taggable_ID   
    Taggable_Type // Constraint: always = 'EntityOne'
    ...
    - - - - - - - -
    FK: (Taggable_ID, Taggable_Type) -> (Taggable.ID, Taggable.Type)

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

person Yarik    schedule 19.11.2008
comment
Гораздо проще сделать столбец Taggable_ID каждой сущности как FOREIGN KEY, так и NOT NULL PRIMARY KEY. Таким образом, вы убедитесь, что все отображается ровно в одну строку в таблице Taggables. Ваш дизайн позволяет отображать несколько рецептов в одну и ту же строку с тегами. - person Bill Karwin; 19.11.2008
comment
Ты прав. Я подробно остановился на своем ответе на ваш комментарий. :-) - person Yarik; 19.11.2008
comment
@ Билл: Похоже, мой ответ на ваше беспокойство - это тоже вариант того, что вы называете полиморфной ассоциацией, верно? - person Yarik; 19.11.2008
comment
Нет, полиморфная ассоциация - это внешний ключ для двух разных родительских таблиц. Какой родитель используется для данной строки, зависит от другого атрибута в дочерней таблице. Конечно, вы не можете использовать обычное ограничение внешнего ключа для полиморфной ассоциации. - person Bill Karwin; 20.11.2008
comment
Что касается вашего решения, я его понимаю, но вы до сих пор не показали, почему вы не можете просто отказаться от столбца ID и вместо этого использовать Taggable_ID в качестве ПК. Не каждой таблице нужен столбец с именем ID. - person Bill Karwin; 20.11.2008
comment
Что, если возможность добавления тегов - не единственная возможность в системе, которая должна быть применима к объектам разных типов? - person Yarik; 20.11.2008
comment
В обычном, нормализованном дизайне БД каждый атрибут представлен в отдельном столбце. Проблема в этом случае заключается в том, что вас поймают, если вы попытаетесь сделать taggable ссылаться на несколько сущностей И заставить сущности ссылаться на множественные -abilites. Мне нужно будет подумать над решением этой проблемы. - person Bill Karwin; 20.11.2008
comment
Честно говоря, меня смутил ваш последний комментарий. :-) - person Yarik; 20.11.2008
comment
Ладно, неважно, я делал это более сложным, чем необходимо. У вас просто есть еще одна таблица пересечений между каждой-возможностью и таблицей Taggables, которая на самом деле не имеет ничего общего с taggable, это просто таблица идентификаторов для всех других сущностей. - person Bill Karwin; 20.11.2008
comment
Я бы сказал немного иначе: я предлагал создать отдельную таблицу пересечений для каждой возможности, которая пересекает несколько сущностей: [Entity1 / Entity2 - ›Taggable ‹-› Tag], [Entity1 / Entity3 -› Huggable ‹-› Обнять] и так далее, и так далее. :-) - person Yarik; 21.11.2008

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

Сущность

  • EntityId
  • Имя

Ингредиент

  • EntityId
  • Количество

Рецепт

  • EntityId
  • SomeOtherInformation

Теперь у вас может быть таблица для пометки сущностей.

person Tjofras    schedule 19.11.2008
comment
Это именно то, что я имею в виду сейчас. Единственное, что меня беспокоит, это то, что таблица Entity может привести к потенциальным взаимоблокировкам. Но я думаю, что могу избежать этого, если не буду хранить важные данные в таблице Entity. - person Saab; 19.11.2008

сделайте обычные таблицы для рецептов, ингредиентов и т. д.

тогда ваша таблица тегов должна выглядеть так: Id, Type, Tag

Я бы рекомендовал использовать перечисление в коде, чтобы различать разные «Типы» (сущности).

person Kyle West    schedule 19.11.2008
comment
Что мне не нравится в этом, так это то, что некоторые данные находятся в базе данных, а некоторые - в коде. Я бы хотел использовать базу данных для вещей, в которых она хороша: для хранения реляционных данных. - person Saab; 19.11.2008
comment
@Kyle: этот дизайн называется полиморфными ассоциациями. Это неправильный дизайн, потому что он путает данные с метаданными. Например. как убедиться, что Type хранит допустимое имя таблицы? - person Bill Karwin; 19.11.2008
comment
Даже если в вашем коде есть перечисление, нет ограничения database, которое гарантирует, что таблица с именем в этом столбце действительно существует. - person Bill Karwin; 20.11.2008
comment
Кроме того, полиморфная ассоциация не позволяет вам объявлять фактический внешний ключ. То есть tag.id ссылается на любую из нескольких таблиц сущностей, но внешний ключ должен указывать одну родительскую таблицу, на которую он ссылается. Таким образом, вы должны обеспечить целостность кода приложения. - person Bill Karwin; 20.11.2008

Как насчет этого?

Типы (PK: Тип, set_id [, TypeDesc])

Атрибуты (PK :( set_id, FK: Тип), значение)

PS: Полужирный / Курсив Realy Suck

person thAAAnos    schedule 19.11.2008
comment
Если я понимаю это, это будет общий способ реализации типов с их атрибутами. Это означало бы, что мне нужен дополнительный слой поверх этого, чтобы его было удобно использовать в коде. - person Saab; 19.11.2008
comment
Это дизайн под названием Entity-Attribute-Value. У него много проблем, и это полный перебор для ситуации, когда у Saab есть только небольшое количество отдельных объектов. - person Bill Karwin; 19.11.2008

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

Две основные таблицы - это элементы (I) и статьи (A). Предметы - это реальные физические предметы, а предметы - производные от предметов. Статьи - это то, что можно отображать на веб-сайте, а предметы - это то, что нужно хранить на складе. Небольшим примером этой взаимосвязи могут быть автомобильные детали. Радиатор с известными размерами и другими данными может соответствовать множеству различных моделей и производителей, поэтому элемент, используемый для обозначения радиатора, относится к нескольким артикулам, которые указывают на то, какой радиатор подходит. С другой стороны, у нас могут быть два разных радиатора для одной модели: один - это новая заводская версия, а другой - только что модернизированный. В таком случае к одной и той же статье относятся два пункта.

Итак, у меня и А есть отношения N: M.

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

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

Радиаторы - это всего лишь пример простого изделия. Мы могли бы также добавить некоторые компьютерные детали или одежду в нашу базу данных. Это означает, что мне нужно иметь возможность ставить разные «теги» на два разных объекта, I и A.

Мне нужно реализовать поиск статей в интернет-магазине. Допустим, я использую древовидную навигацию, где у меня есть категория под названием «Подержанные радиаторы Nissan». Поиск будет включать поиск как статей, так и предметов, статьи имеют тег Model: Nissan, а предметы имеют тег Condition: Used. Конечно, когда пользователь просматривает статью, он действительно видит все элементы, связанные со статьей.

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

У нас есть элементы таблиц (I), статьи (A) и теги (T). Они объединены отношениями N: M: I2A соединяет элементы со статьями. T2I присоединяет теги к элементам и может также хранить значение тега или свойства. T2A присоединяет теги к статьям и может также хранить значение тега.

На бумаге этот дизайн с 6 таблицами для решения этой проблемы выглядит неплохо, но у меня возникает головная боль при формировании приличного запроса, где я могу выбрать статьи, соответствующие набору разных тегов и их значений, например: Condition = Remanufactured , Марка = Nissan

Я хочу иметь возможность делать что-то вроде www.summitracing.com. Выберите «Отделы» слева под «Магазином», выберите любую категорию, и вы увидите, как им удалось придать предметам некоторые свойства. У них есть размер двигателя для большинства применений, но при поиске колесных дисков у них также есть свойство ширины.

Мы будем очень благодарны за любые отзывы по этому поводу, я начинаю поражать воображение, пытаясь разработать это.

person Community    schedule 15.02.2009