【问题标题】:design database relating to time attribute与时间属性相关的设计数据库
【发布时间】:2011-05-04 06:21:14
【问题描述】:

我想设计一个数据库,描述如下: 每个产品在一个时间点只有一个状态。但是,产品的状态可能会在其生命周期内发生变化。如何设计产品和状态之间的关系,可以轻松查询当前特定状态的所有产品?另外,谁能给我一些关于设计数据库的深入细节,这些设计数据库与上述问题的持续时间有关?感谢您的帮助

【问题讨论】:

    标签: mysql sql-server database database-design


    【解决方案1】:

    类似于这些的表格:

    product
    -----------
    product_id
    status_id
    name
    
    status
    -----------
    status_id
    name
    
    product_history
    ---------------
    product_id
    status_id
    status_time
    

    然后在产品上编写一个触发器来记录状态变化的每次更新的状态和时间戳(sysdate)

    【讨论】:

      【解决方案2】:

      谷歌“双时态数据库”和“缓慢变化的维度”。

      这是本质上相同模式的两个名称。

      您需要在产品表“VALID_FROM”和“VALID_TO”中添加两个时间戳列。

      当您的产品状态发生变化时,您添加一个带有 now() 的“VALID_FROM”的新行,以及一些其他已知的有效数据/时间,并将“VALID_TO”设置为 9999-12-31 23:59:59 或其他一些可笑的日期遥远的未来。 您还需要将先前当前行上的“9999-12-31...”日期转换为当前的“VALID_FROM”时间 - 1 微秒。

      您可以随时轻松查询产品状态。

      【讨论】:

      • @James:我对你的答案的概念没有意见。但是第二列是 100% 重复的(表将是它需要的两倍大):它可以从下一行的第一列派生。此外,如果删除第二列,则不考虑微秒等;可以使用任何相关的数据类型(DATE、SMALLDATETIME、DATETIME)。
      • @;桌子大两倍;或一半适合相同的缓存空间;物理磁盘 I/os 的两倍。:也许我遗漏了一些东西,您能否发布“痛苦的”SQL 来导出 VaidTo 值。在任何情况下,这都不是 100% 重复的有效或合理理由
      • @James:我明白这一切。 1. 对“快捷方式”进行编码,特别是在容易得出值的情况下,对于 100% 重复列不是有效的理由。 2.如果桌子很大,那就是大空间的两倍。 3. 性能减半。 4. 你破坏了规范化。 5.您已经介绍了错误的可能性(现在可以通过两种方式得出值)。不可接受和不正确的建议。 -1 在我指出之后争论而不修复。
      • @詹姆斯。 OMG,所以你已经复制了整个产品表乘以产品版本的数量。好笑。让我猜猜,只有当前的 Product 版本,虚拟值 9999-12-31 具有真正的 Product 列,表中的其他所有内容(期望历史状态和时间范围)都是错误的!这就是你所说的数据库“设计”,嗯。毫无疑问,“标准化”。通过大量复制实现“高性能”。一切为了捷径。
      • @James:这个问题是数据库问题,而不是数据仓库问题。在(读取密集型)DW 中可能可以接受的大量低效设计不适用于现代数据库。我的大多数服务器都是 SPARC,按照你的速度,你永远看不到一台;以我们付出的代价,我们不允许 100% 重复之类的愚蠢事情。胖子不参加奥运会。所以这个 Kimball 家伙实际上 建议 重复原子事实,是吗?他真的见过数据库吗?这意味着听起来他从非数据库开始,并使其变得更好。
      【解决方案3】:

      【讨论】:

      • 和 James 一样,ValidToDate 是多余的,它可以很容易地推导出来。 IsCurrentStatus 同样是多余的,并且可以很容易地导出( Max(ValidFromDate) )。
      • @PerformanceDBA -- 是的,但是说WHERE IsCurrentStatus = 1 比派生它更快更简单。
      • 在一些(臃肿的)DW 中可能是可以接受的。但是在 RDB 中,它并没有快得多,甚至快一点。这是错误的。不必要地 100% 重复列是荒谬的,使磁盘空间和物理 I/O 翻倍。根据您的 PK,这些行彼此相邻,并保证在同一页面上,并同时在缓存中。只有一半的表适合任何给定的缓存大小,因此逻辑 I/O 的速度也慢了一倍。
      【解决方案4】:

      “另外,谁能给我一些关于设计数据库的深入细节,与上述问题的持续时间有关?”

      嗯,有一本 400 页的书,题为“时间数据和关系模型”,可以解决您的问题。

      这本书还解决了其他响应者在他们的回答中没有解决的许多问题,因为时间不够,空间不足或知识不足。

      这本书的介绍还明确指出“这本书不是关于当今任何用户都可以(商业上)使用的技术。”。

      我所能观察到的是,想要从 SQL 系统获得时间特征的用户,简单地说,就是不想要。

      PS

      即使这 400 页可以“压缩一点”,我希望你不要指望我在 SO 上的几段中给出整个有意义的内容的摘要 ...

      【讨论】:

        【解决方案5】:

        这里有一个模型可以满足您的要求。

        Link to Time Series Data Model

        Link to IDEF1X Notation 适用于不熟悉关系建模标准的人。

        • 归一化为 5NF;没有重复的列;没有更新异常,没有 Null。

        • 当产品的状态发生变化时,只需在 ProductStatus 中插入一行,即当前的日期时间。无需触摸之前的行(这些行是真的,并且仍然是真的)。报告工具(您的应用除外)无需解释任何虚拟值。

        • 日期时间是产品处于该状态的实际日期时间; “从”,如果你愿意的话。 “To”很容易得出:它是 Product 的下一个(DateTime > “From”)行的 DateTime;如果不存在,则值为当前的 DateTime(使用 ISNULL)。

        第一个模型完成; (ProductId, DateTime) 足以为主键提供唯一性。但是,由于您对某些查询条件要求速度,我们可以在物理层面增强模型,并提供:

        • 一个索引(我们已经有了 PK 索引,因此我们将首先对其进行增强,然后再添加第二个索引)以支持覆盖查询(可以提供基于 { ProductId | DateTime | Status } 的任何排列的查询)按索引,而不必转到数据行)。这会将 Status::ProductStatus 关系从 Non-Identifying(虚线)更改为 Identification type(实线)。

        • 根据 Product⇢DateTime⇢Status 大多数查询将是时间序列来选择 PK 安排。

        • 提供第二个索引以提高基于状态的查询速度。

        • 在替代安排中,这是相反的;即,我们主要想要所有产品的当前状态。

        • 在 ProductStatus 的所有版本中,二级索引(不是 PK)中的 DateTime 列是 DESCending;最新的在前。

        我已经提供了您要求的讨论。当然,您需要尝试一个合理大小的数据集,并做出自己的决定。如果这里有什么不明白的,请追问,我会展开的。

        评论回复

        报告当前状态为 2 的所有产品

        SELECT  ProductId,
                Description
            FROM  Product       p,
                  ProductStatus ps
            WHERE p.ProductId = ps.ProductId  -- Join
            AND   StatusCode  = 2             -- Request
            AND   DateTime    = (             -- Current Status on the left ...
                SELECT MAX(DateTime)          -- Current Status row for outer Product
                    FROM  ProductStatus ps_inner
                    WHERE p.ProductId = ps_inner.ProductId
                    )
        • ProductId 已编入索引,前导列,两侧

        • DateTime in Indexed, 2nd col in Covered Query Option

        • StatusCode 已编入索引,在 Covered Query Option 中的第 3 列

        • 由于Index中的StatusCode是DESCending,所以只需要一次fetch就可以满足内部查询

        • 对于一个查询,同时需要这些行;它们靠得很近(由于聚集索引);由于行大小较短,几乎总是在同一页面上。

        这是普通的SQL,一个子查询,利用SQL引擎的强大,关系集处理。这是一种正确的方法,没有比这更快的方法了,任何其他方法都会更慢。任何报告工具只需点击几下即可生成此代码,无需输入。

        ProductStatus 中有两个日期

        DateTimeFrom 和 DateTimeTo 等列是严重错误。让我们按重要性排序。

        1. 这是一个严重的标准化错误。 "DateTimeTo" 很容易从下一行的单个 DateTime 导出;因此它是多余的,是重复的列。

          • 不考虑精度:这很容易通过 DataType(DATE、DATETIME、SMALLDATETIME)解决。无论您是显示少一秒、微秒还是纳秒,都是一项业务决策;它与存储的数据无关。
        2. 实现 DateTo 列是 100% 的重复(下一行的 DateTime)。这需要 两倍的磁盘空间。对于一张大桌子,这将是非常不必要的浪费。

        3. 鉴于它是一个短行,每次访问时,您都需要 两倍的逻辑和物理 I/O 来读取该表。

        4. 并且缓存空间增加一倍(或者换句话说,任何给定的缓存空间只能容纳一半的行数)。

        5. 通过引入重复列,您已经引入了错误的可能性(该值现在可以通过两种方式得出:从重复的 DateTimeTo 列或下一行的 DateTimeFrom)。

        6. 这也是一个更新异常。当您更新任何 DateTimeFrom 为已更新时,必须获取上一行的 DateTimeTo(没什么大不了的,因为它很接近)和已更新(大不了,因为它是可以避免的附加动词)。

          李>
        7. “更短”和“编码快捷方式”无关紧要,SQL 是一种繁琐的数据操作语言,但 SQL 是我们所拥有的一切(只需处理它)。任何不能编写子查询的人真的不应该编写代码。任何复制列以减轻较小的编码“困难”的人确实不应该对数据库进行建模。

        请注意,如果维护了最高阶规则(标准化),则整个低阶问题集都将被消除。

        从集合角度思考

        • 任何在编写简单 SQL 时遇到“困难”或经历“痛苦”的人在执行其工作职能时都会受到影响。通常,开发人员考虑集合,而关系数据库是面向集合的模型

        • 对于上面的查询,我们需要当前日期时间;由于 ProductStatus 是按时间顺序排列的 set 个 Product State,我们只需要属于该 Product 的 set 的最新的或 MAX(DateTime)。

        • 现在让我们看一下所谓的“困难”,就sets而言。对于每个产品处于特定状态的持续时间的报告:DateTimeFrom 是一个可用列,并定义了水平截止值,一个子 set(我们可以排除较早的行); DateTimeTo 是产品状态的子集合中最早的。

        SELECT               ProductId,
                             Description,
                [DateFrom] = DateTime,
                [DateTo]   = (
                SELECT MIN(DateTime)                        -- earliest in subset
                    FROM  ProductStatus ps_inner
                    WHERE p.ProductId = ps_inner.ProductId  -- our Product
                    AND   ps_inner.DateTime > ps.DateTime   -- defines subset, cutoff
                    )
            FROM  Product       p,
                  ProductStatus ps
            WHERE p.ProductId = ps.ProductId 
            AND   StatusCode  = 2             -- Request
        • 考虑获取下一行是面向行的,不是面向集合的处理。使用面向集合的数据库时,严重不足。让优化器为您完成所有这些想法。检查你的 SHOWPLAN,优化得很漂亮。

        • 无法在 sets 中思考,因此仅限于编写单级查询,这不是一个合理的理由:在数据库中实现大量复制和更新异常;浪费在线资源和磁盘空间;保证一半的性能。学习如何编写简单的 SQL 子查询以获取易于派生的数据要便宜得多。

        【讨论】:

        • @Tony: 1) 我们已经从另一个最近的线程中知道,Oracle 会自行执行子查询。 (顺便说一句,这回答了我过去提出的许多问题,即为什么 Oracle 要求对基准要求进行某些更改。所以难怪 Oracle 男孩为了避免重复列而实施重复列。所以说 Oracle 会执行BETWEEN 版本以相同的速度;事实是 oracle 无法执行子查询版本。2)Sybase、DB2 和 MS 对子查询没有问题(好吧,MS 在 2008 年严重破坏了它,但是“等等为下一个 SP”。)
        • 3) 我在回答中提供了所需(典型)子查询的结构。我对查询计划非常熟悉,与最近在另一个问题上发布的相同。 DB2 和 Sybase 具有非常成熟的优化器,可以产生高度规范化的 QP。不确定这是否能回答您的问题?你想让我做一个测试吗?
        • @Tony。 1) 但是这些标量子查询! 3)您确实意识到从中提取 MAX() 的集合是(a)小(b)相关的,因此(c)它将在同一次扫描中完成,并且(d)几乎可以肯定在缓存中,不要你。 4) 好的,所以你想测试 LIO 和处理,就像上次测试一样,PIO 不相关?您想要 3 个 DDL 选项中的哪一个(参考链接,我建议 DateTime 不是前导列,否则没有挑战)?并且请通过电子邮件将您想要的确切扁平化查询与 BETWEEN 或其他内容通过电子邮件发送给我,它已经很晚了,我无法以非设定的方式思考,抱歉。
        • @Tony: 1) 但是这些 SELECT 列表中的标量子查询! 3)啊哈。任何组合:检查上次测试的统计摘要;或检查我通过电子邮件发送给您的查询计划;或等待测试结果。对于您而言,我觉得如果您关闭上次测试中的未解决问题,这将有利于您对该主题的增量知识。 做好准备,我们在门口,我要和你的老伙伴一起擦地板。 4) 不,它以 DateTime 为主导列,所以根本没有挑战,让我们让 Sybase 为钱工作;我建议第二个;减去第二个索引。
        • 评论格式的限制让这变得比应该的更难,所以我提出了一个单独的问题:stackoverflow.com/questions/4375192/…
        猜你喜欢
        • 1970-01-01
        • 2011-05-05
        • 1970-01-01
        • 1970-01-01
        • 2011-02-26
        • 2020-07-18
        • 2011-05-27
        • 1970-01-01
        • 2011-02-17
        相关资源
        最近更新 更多