我应该为不同的实体状态、状态或阶段创建多个表吗?

Mac*_*y18 4 mysql database-design

我的tasks数据库中有一个表,在感兴趣的业务领域中,任务可以有多种状态:“打开”、“开始”、“审查中”和“已完成”。

尽管在同一个表中有“打开”和“开始”,但使用标志标识,但出于某种原因,我很早就决定将“审查中”和“已完成” 任务放在他们自己的单独表中。虽然这似乎是一个好主意,可以轻松查询特定事物,但当我考虑为任务实现注释功能时,我意识到值会在三个表中发生变化。task_id

我做错了吗?

当前的考虑

我想到的一些解决方案是重做组织并将所有内容放在一张桌子上,使用标志来纯粹区分状态(这将导致大量的返工);创建某种UUID可以跨表转移的东西 - 尽管我认为如果这样做可能会导致性能问题;最后,将IDs跨表设置为不再自动递增,并简单地“继承”第一个task表(自动递增)的原始值。

在这种情况下做了什么?我不是最精通数据库设计的人,而且我在进行过程中正在编造这一点。

任何帮助将不胜感激。

对@MDCCL 通过评论提出的进一步信息请求的回应

因此,状态(或状态)一个的任务,可以随着时间而改变,对不对?同一个具体的任务可以出现多少次相同的状态,比如“打开”?它会根据相关任务呈现的状态类型而变化吗?

是的,一个task.state理论上最多可以改变四次。我不会详细介绍逻辑,但最长的链将是“begun” ? “completed” ? “review” ? “completed”. 每次,目前,它们都被放入另一张桌子。

例外的是,无论它是否“开始”,用户都可能会无限地放弃然后重新执行任务——至少在理论上是这样。这至少得益于“开始”状态是一个标志这一事实。

好的,但是某个任务将始终呈现一个特定的“当前”状态,对吗?例如,当task一行被插入时,它应该伴随一个特定的state值,我假设它是“打开的”。稍后,此类任务状态将演变为仅其他可能选项之一,即“开始”或“审核中”或“已完成”。

是的,完全正确。一个任务永远是在一个阶段,不能在多个。

一旦某个特定任务被视为“已完成”,它是否可以更改为另一种状态

是的,如果达到某些参数,可以将其发送回“审查”。从“审查”,它可以回到“开始”或“完成”。

MDC*_*CCL 9

就我看到它,事实上,(一)具体的任务可呈现不同的国家在时间上造成采用不同的点(B)的创建是要包含一个表中的时间序列,因为我将在下面详细。

商业规则

为了定义关系数据库的结构和约束,首先要准确地识别和制定相应的业务领域规则(即描绘概念模式),同时考虑到相关的实体类型属性和相应的 互连。 . 这样,在所考虑的场景中特别重要的一些公式是:

  • 一个Task呈现一对多的StateSpecifications
  • 一个Task不能在同一个Instant呈现多个StateSpecification
  • 一个StateSpecification被认为是当前
    • 从它被输入的特定瞬间
    • 直到输入连续的StateSpecification时 的确切Instant
  • 一个State定义了零一或多个StateSpecifications
  • 任务接收零一或一对多评论

因此,基于上述公式,我创建了 IDEF1X 图,如图 1所示:

图 1 - 任务状态和注释 IDEF1X 图

如您所见,Task并被State描述为单独的实体类型,每个实体类型都有自己的一组属性(或属性)和自己的(直接)关联(或关系),它们通过相应的动词短语基数和 FOREIGN 表示KEY (FK) 标记。

命名的实体类型StateSpecification与之前提出的两个相关联,是我将在下面解释的解决方案的核心方面。

说明性逻辑设计和样本数据

我根据上面的 IDEF1X 图,通过以下 DDL 结构塑造了一个说明性逻辑设计:

-- You should determine which are the most fitting 
-- data types and sizes for all your table columns 
-- depending on your business context characteristics.

-- Also, you should make accurate tests to define the most
-- convenient physical implementation settings; e.g.,
-- a good indexing strategy based on query tendencies.

-- As one would expect, you are free to use your 
-- preferred or required naming conventions. 

CREATE TABLE Task (
    TaskNumber      INT      NOT NULL,
    Description     CHAR(90) NOT NULL,
    Etcetera        CHAR(30) NOT NULL,
    CreatedDateTime DATETIME NOT NULL,
    --
    CONSTRAINT Task_PK  PRIMARY KEY (TaskNumber)
);    

CREATE TABLE State (
    StateCode   CHAR(1)  NOT NULL,
    Name        CHAR(30) NOT NULL,
    Description CHAR(60) NOT NULL,
    --
    CONSTRAINT State_PK  PRIMARY KEY (StateCode),
    CONSTRAINT State_AK1 UNIQUE      (Name),       -- ALTERNATE KEY.
    CONSTRAINT State_AK2 UNIQUE      (Description) -- ALTERNATE KEY.
); 

CREATE TABLE StateSpecification (
    TaskNumber        INT      NOT NULL,
    SpecifiedDateTime DATETIME NOT NULL,
    StateCode         CHAR(1)  NOT NULL,
    --
    CONSTRAINT StateSpecification_PK         PRIMARY KEY (TaskNumber, SpecifiedDateTime), -- Composite PRIMARY KEY.
    CONSTRAINT StateSpecification_to_Task_FK FOREIGN KEY (TaskNumber)
        REFERENCES Task  (TaskNumber),
    CONSTRAINT StateSpecification_to_State   FOREIGN KEY (StateCode)
        REFERENCES State (StateCode)    
);

CREATE TABLE TaskComment (
    TaskNumber      INT      NOT NULL,
    CreatedDateTime DATETIME NOT NULL,
    Body            TEXT     NOT NULL,
    --
    CONSTRAINT TaskComment_PK      PRIMARY KEY (TaskNumber, CreatedDateTime),
    CONSTRAINT TaskComment_to_Task FOREIGN KEY (TaskNumber)
        REFERENCES Task (TaskNumber)  
);

--
--
Run Code Online (Sandbox Code Playgroud)

状态表

State前面说明的表起到了查找的作用,它包含五行(根据您提供的五个样本):

 +-—————————-+-—————————-+-———————————————-+
 | 状态代码| 姓名      | 描述     |
 +-—————————-+-—————————-+-———————————————-+
 | 哦| 打开 | 表示…… |
 +-----------+-----------+-----------------+
 | 乙 | 开始 | 表示…… |
 +-----------+-----------+-----------------+
 | R | 在审查 | 表示…… |
 +-----------+-----------+-----------------+
 | C | 已完成 | 表示…… |
 +-----------+-----------+-----------------+

请注意保留值的列上的 PRIMARY KEY (PK) 约束的定义,这些值同时是meaninfgul(关于最终用户和技术人员的解释)和(就物理实现级别的字节而言),这使得它在例如数据检索方面既可读又快速。

如果有新的业务领域的国家出现,你当然可以插入相应的行插入到State表。

状态规格表

现在,假设任务没有。1750以表中保存的(无序)行的形式呈现以下状态历史StateSpecification

 +-———————————-+-———————————————————————————-+-————————— -+
 | 任务编号| 指定日期时间       | 状态代码|
 +-———————————-+-———————————————————————————-+-————————— -+
 | 1750 | 2016-12-01 16:58:12.000 | 哦|
 +------------+-------------------------+--------- -+
 | 1750 | 2016-12-02 09:12:05.000 | 乙 |
 +------------+-------------------------+--------- -+
 | 1750 | 2016-12-04 10:57:01.000 | R |
 +------------+-------------------------+--------- -+
 | 1750 | 2016-12-07 07:33:08.000 | C |
 +------------+-------------------------+--------- -+
 | 1750 | 2016-12-08 12:12:09.000 | R |
 +------------+-------------------------+--------- -+
 | 1750 | 2016-12-08 19:46:01.000 | 乙 |
 +------------+-------------------------+--------- -+
 | 1750 | 2016-12-09 06:24:07.000 | R |
 +------------+-------------------------+--------- -+
 | 1750 | 2016-12-11 07:24:07.000 | C |
 +------------+-------------------------+--------- -+

如图所示,SpecifiedDateTime列中的每个值表示给定任务(通过TaskNumber)开始呈现特定状态(凭借StateCode)的精确时间点。

看到该StateCode列保留了具有非常明确意图的值,它有助于节省 JOIN 子句的使用。这是极为重要的明确,这并不能意味着连接是什么不利(他们,事实上,在任何关系型数据库基础和强有力的手段),但是,只要合适,降低了系统的宝贵资源的消耗是坚决实用.

评论表

Comment如上所述,该表可以很容易地Task通过 FK 约束定义与该表相关联,因为 a 的 PK 值Task将保持不变,无论其状态是否经历零次、一次或多次修改。

我假设,在相关的业务上下文中,评论(a) 将始终取决于任务事件的存在,并且 (b) 将始终属于同一单个任务事件;因此,我Comment使用复合 PK定义了该表,该组合 PK 包含 PK 列Task以及创建DateTime特定项的确切位置Comment。除此之外,您可能想评估系统控制代理的列(例如,MySQL 中具有AUTO_INCREMENT属性的列)到此表的附件,它可能会或可能不会提供更好的执行性能(可能不会),所以进行一些彻底的测试会澄清情况(a- 数据添加,就像包含系统控制代理的列的情况一样,应始终进行充分评估和证明)。

数据推导代码示例

对于所讨论的场景,很可能需要一些重要的数据点,但它们应该作为派生(或计算)值获得。

例如,如果你需要得到CurrentState任务没有。1750,您可以使用下一个 SELECT 语句派生它:

SELECT T.TaskNumber,
       SS.StateCode AS CurrentStateCode,
       SS.SpecifiedDateTime
     FROM Task T
     JOIN StateSpecification SS
       ON T.TaskNumber = SS.TaskNumber 
    WHERE T.TaskNumber = 1750 -- You can provide a parameter instead of a fixed value.
      AND SS.SpecifiedDateTime = (
                                    SELECT MAX(SpecifiedDateTime)
                                          FROM StateSpecification InnerSS
                                         WHERE T.TaskNumber = InnerSS.TaskNumber
                                 );
Run Code Online (Sandbox Code Playgroud)

当您需要CurrentState为所有任务获取 时,您可以声明如下语句:

SELECT T.TaskNumber,
       SS.StateCode AS CurrentStateCode,
       SS.SpecifiedDateTime
     FROM Task T
     JOIN StateSpecification SS
       ON T.TaskNumber = SS.TaskNumber
    WHERE SS.SpecifiedDateTime = (
                                    SELECT MAX(SpecifiedDateTime)
                                          FROM StateSpecification InnerSS
                                         WHERE T.TaskNumber = InnerSS.TaskNumber
                                 );
Run Code Online (Sandbox Code Playgroud)

如果您必须导出StartDate任务编号相对应EndDate的所有和1750,您可以使用:StateSpecifications

SELECT T.TaskNumber,
       T.Description,
       SS.StateCode,
       SS.SpecifiedDateTime AS StartDateTime,
       (
          SELECT MIN(SpecifiedDateTime)
                FROM StateSpecification InnerSS
               WHERE T.TaskNumber = InnerSS.TaskNumber
                 AND InnerSS.SpecifiedDateTime > SS.SpecifiedDateTime
       ) AS EndDateTime
    FROM Task T
    JOIN StateSpecification SS
      ON T.TaskNumber = SS.TaskNumber
   WHERE T.TaskNumber = 1750 -- You can provide a parameter instead of a fixed value.
ORDER BY StartDateTime DESC;
Run Code Online (Sandbox Code Playgroud)

如果您必须获得StartDateEndDate所有的StateSpecifications,即整个时期中,他们目前的

SELECT T.TaskNumber,
       T.Description,
       SS.StateCode,
       SS.SpecifiedDateTime AS StartDateTime,
       (
          SELECT MIN(SpecifiedDateTime)
                FROM StateSpecification InnerSS
               WHERE T.TaskNumber = InnerSS.TaskNumber
                 AND InnerSS.SpecifiedDateTime > SS.SpecifiedDateTime
       ) AS EndDateTime
    FROM Task T
    JOIN StateSpecification SS
      ON T.TaskNumber = SS.TaskNumber
ORDER BY StartDateTime DESC;
Run Code Online (Sandbox Code Playgroud)

查看定义

自然地,上面显示的一个或多个 SELECT 语句可以设置为 VIEW,这样您就可以直接从单个表中获得相关信息(派生的,是的,但它仍然是一个表) .

演示

在此 SQL Fiddle(在 MySQL 5.6 上运行)和此 db<>fiddle(在 Microsoft SQL Server 2014 上运行,如果有人感兴趣的话)中创建了代码的现场演示。

对评论的回应

我想我将不得不执行此修改,并认为系统将受益于StateSpecification表的附加功能。它表明良好的数据库设计可以满足我们的所有需求,而不会使事情变得混乱。

我完全同意你的第二个断言。设计得当(概念上、逻辑上和物理上)的数据库通常会成为软件开发项目中的关键元素。如果关系数据库准确地反映了感兴趣的业务上下文,那么它可以——简而言之——大大简化其维护并提供广泛的多功能性。这就是为什么它是仔细分析相关的结构非常有用的东西,观察他们来讲是什么,他们是在现实世界中,然后用精密在数据库中执行代表他们。

数据本身就是一种非常有价值的组织资产,因此,它应该像这样进行管理。实现上述目标的可靠方式是采用有可靠理论支持的技术手段,在数据管理领域,没有什么比关系模型更可靠的。这与EF Codd 博士1981 年的图灵奖演讲一致,题为Relational Database: A Practical Foundation for Productivity

另外,为了澄清一下StateSpecification,必须在每次状态更改时手动更新该表,对吗?

基本上是的,StateSpecification每次任务呈现状态更改时都必须更新表格。此过程应通过引入新StateSpecification行的 INSERT 操作来执行,该行应包含指向Task应用行的 FK 值。

它是适当时候的应力,当Task行被插入,它必须呈现StateSpecification指示所述状态视为“打开”(通过“O”值的力保留在StateCode柱),因此你应该利用良好定义的ACID TRANSACTIONS以便将Task行及其StateSpecification对应项视为一个单元。以这种方式,两个各自的 INSERT 操作作为一个整体可以成功也可以失败,从而使所涉及的数据与识别的业务规则保持一致

可比场景

你可能会找到帮助

因为它包括一个具有相似特征的案例。


尾注

信息建模集成定义( IDEF1X ) 是一种非常值得推荐的数据建模技术,美国国家标准与技术研究院(NIST)于 1993 年 12 月将其确立为标准。它牢固地基于 (a) 关系模型的创始人,即 EF Codd 博士撰写的早期理论工作;关于 (b)实体关系视图,由PP Chen 博士开发;以及 (c) 逻辑数据库设计技术,由 Robert G. Brown 创建。