Вопрос проектирования базы данных

#database-design

#база данных-проектирование

Вопрос:

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

Каждая история заканчивается испытанием.

Для представления всего в реляционной базе данных я использовал следующие таблицы:

 Story
---------------------------------------
Id              | PK
Name            | String
FirstActId      | FK to ACT table

Act
---------------------------------------
Id              | PK
StoryId         | FK to STORY table
Name            | String
Description     | Very long string
NextActionType  | FK to ACTIONTYPE table
NextId          | Its value depends on NextActionType

ActionType
---------------------------------------
Id              | PK
Name            | Values= 'Act', 'Challenge', 'Story'.

Challenge
---------------------------------------
Id              | PK
Name            | String
Description     | Very long string
NextActionType  | FK to ACTIONTYPE table
NextId          | Its value depends on NextActionType
  

Я использую Story.FirstActId , чтобы узнать, какой первый акт для истории (я пытаюсь сказать, что это "Select MIN(Id) from Act where StoryId = ?" не работает.).

И затем я использую Act.NextId , чтобы узнать, какое действие или задача выполняется после этого действия. Act.NextId это может быть Акт PK, или Задача PK, или история PK. Это зависит от Act.NextActionType значения.

Что вы думаете? Хороший ли это дизайн?

Моя проблема в том, что у меня есть таблица Challenge без какой-либо связи с какой-либо другой таблицей.

Обновить

Другими словами, мне нужна система, чтобы знать, куда двигаться дальше. У меня будут следующие ситуации:

история-> действие-> вызов-> действие -> новая история история-> действие-> вызов-> новая история

ПРИМЕЧАНИЕ: act может быть более одного.

Комментарии:

1. Если вы не знаете, какой акт является первым, вторым и т.д. как вы можете сохранить FirstActId?

2. Я знаю, какое действие выполняется первым, когда я добавляю истории и действия. Я пытаюсь сказать, что «Выберите MIN (Id) из Act, где StoryId = ?» не работает.

Ответ №1:

Дизайн вашей таблицы не соответствует 3NF и может привести к аномалиям обновления (например, если удалить первый акт истории, вся цепочка будет разорвана; если обновить ее NextActId, цепочка полностью изменится). Другая проблема заключается в том, что вы должны рекурсивно искать в цепочке, чтобы получить все действия для истории, а не получать их в одном запросе.

Если вы хотите иметь возможность повторно использовать действия между историями, вам следует использовать таблицу косвенных ссылок:

 StoriesActsMap
--------------
storyid
actid
ordinal_number
  

Если вам не нужно повторно использовать acts между историями, то вы можете просто придерживаться таблицы Acts:

    Act
   -------------
   actid
   actDescription, etc.
   ordinal_number
   storyid
  

Обработка проблем также зависит.

Может ли вызов также быть действием? Если это так, вы должны сохранить его в Acts и иметь таблицу ActsType:

    Act
   --------------
   actid
   acttypeid
   ...see rest of Act above

   ActsType
   --------------
   acttypeid
   acttype (Act, Challenge)
  

Если вызов — это действие, исключается ли его определение как вызова из-за его позиции в истории (последней)? Если это так, ActsType не является необходимым, это будет просто МАКСИМАЛЬНЫЙ порядковый номер в StoriesActMap / Act

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

Предполагая, что задача всегда является задачей и действия / вызовы не могут быть разделены между историями, дизайн будет следующим:

 Story
----------
storyid
other info

Act
----------
actid
storyid
actorder (ordinal number of acts, (actorder, storyid) is a prime)
other info

Challenge
----------
chid
storyid
other info
  

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

Комментарии:

1. После прочтения вашего обновления вопроса, мой дизайн не обрабатывает историю, следующую за действием. Всегда ли история следует за вызовом? Или история может быть следующим действием в любое время?

2. Я использую nextActionType и NextID для настройки «навигации», которая у меня может быть: история-> действие-> вызов-> действие-> новая история. Или этот: история-> действие-> вызов-> новая история. Действий может быть несколько. Я не уверен, что понял ваш вопрос. Если вам нужны более подробные сведения, скажите мне.

Ответ №2:

Я бы изменил некоторые вещи:

 Act
---------------------------------------
...
NextAct         | FK to Act table, can be NULL
Challenge       | FK to Challenge table, can be NULL

No need of ActionType
  

Вы сэкономите место. За действием следует действие, если NextAct не равен null,
за действием следует вызов, если вызов не равен null.

Конечно, условие (NextAct xor Challenge) всегда должно быть истинным. Вы можете подтвердить это, добавив табличное ограничение на вставки в таблицу Act.

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

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

 SELECT
  challenge_id,
  act_id,
  ...
FROM
  Act
LEFT OUTER JOIN
  Act NextAct ON Act.next_act_id = NextAct.act_id
...
LEFT OUTER JOIN
  Challenge ON Act.challenge_id = Challenge_id
  

По крайней мере, один не будет равен NULL.

Комментарии:

1. Я забыл сказать, что nextActionType также может быть историей. Я обновил свой вопрос с более подробной информацией. Извините.

2. Это ничего не меняет, древовидная / графическая структура должна быть создана с отношениями Дочерние элементы -> Parent, а не Parent -> Children.

Ответ №3:

Мне кажется, что Acts amp; Challenges хранят информацию одного и того же типа, поэтому размещение их в разных таблицах кажется излишним.

Я бы, вероятно, выбрал..

 Story
---------------------------------------
Id              | PK
Name            | String
FirstActId      | FK to ACT table

Items (Acts or Challenges)
---------------------------------------
Id              | PK
StoryId         | FK to STORY table
Name            | String
Description     | Very long string
Order           | Int - which position this Act / Challenge should go in
  

У вас мог бы быть флаг, указывающий, является ли что-либо действием или вызовом.

Однако, если я понимаю ваше описание, тогда вы могли бы идентифицировать элементы, используя соглашение, например, установив для первого элемента Order значение 0, что делает его первым действием, и когда Действие / вызов является последним элементом в наборе (т. Е. Max(Order) ), обозначающим его как вызов.

Ответ №4:

На мой взгляд, Challenge это просто (под) тип Act . Учитывая, что Act таблица содержит все столбцы, необходимые в Challenge , вы можете просто удалить таблицы Challenge и ActionType . Используйте ThisActionType столбец вместо, NextActionType который мог бы содержать C,A , если вам нужно знать, что это за действие (стандартное или вызовное).

Также избавьтесь от FirstActID из таблицы Story и найдите способ идентифицировать первый / последний акт в Act таблице — каждый акт может принадлежать только одной истории в этой модели, и первый и последний, очевидно, являются особыми.

Итак, к тому времени, когда это будет сделано, это будет выглядеть примерно так

введите описание изображения здесь

ActPosition (F, L, A) будет использоваться для определения первого, последнего, любого.

Комментарии:

1. Я забыл сказать, что nextActionType также может быть историей. Я обновил свой вопрос с более подробной информацией. Извините.

2. @VansFannel, В этом случае NextActID просто указывало бы на первый акт другого StoryID .