#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
.