【发布时间】:2011-05-04 06:21:14
【问题描述】:
我想设计一个数据库,描述如下: 每个产品在一个时间点只有一个状态。但是,产品的状态可能会在其生命周期内发生变化。如何设计产品和状态之间的关系,可以轻松查询当前特定状态的所有产品?另外,谁能给我一些关于设计数据库的深入细节,这些设计数据库与上述问题的持续时间有关?感谢您的帮助
【问题讨论】:
标签: mysql sql-server database database-design
我想设计一个数据库,描述如下: 每个产品在一个时间点只有一个状态。但是,产品的状态可能会在其生命周期内发生变化。如何设计产品和状态之间的关系,可以轻松查询当前特定状态的所有产品?另外,谁能给我一些关于设计数据库的深入细节,这些设计数据库与上述问题的持续时间有关?感谢您的帮助
【问题讨论】:
标签: mysql sql-server database database-design
类似于这些的表格:
product
-----------
product_id
status_id
name
status
-----------
status_id
name
product_history
---------------
product_id
status_id
status_time
然后在产品上编写一个触发器来记录状态变化的每次更新的状态和时间戳(sysdate)
【讨论】:
谷歌“双时态数据库”和“缓慢变化的维度”。
这是本质上相同模式的两个名称。
您需要在产品表“VALID_FROM”和“VALID_TO”中添加两个时间戳列。
当您的产品状态发生变化时,您添加一个带有 now() 的“VALID_FROM”的新行,以及一些其他已知的有效数据/时间,并将“VALID_TO”设置为 9999-12-31 23:59:59 或其他一些可笑的日期遥远的未来。 您还需要将先前当前行上的“9999-12-31...”日期转换为当前的“VALID_FROM”时间 - 1 微秒。
您可以随时轻松查询产品状态。
【讨论】:
【讨论】:
WHERE IsCurrentStatus = 1 比派生它更快更简单。
“另外,谁能给我一些关于设计数据库的深入细节,与上述问题的持续时间有关?”
嗯,有一本 400 页的书,题为“时间数据和关系模型”,可以解决您的问题。
这本书还解决了其他响应者在他们的回答中没有解决的许多问题,因为时间不够,空间不足或知识不足。
这本书的介绍还明确指出“这本书不是关于当今任何用户都可以(商业上)使用的技术。”。
我所能观察到的是,想要从 SQL 系统获得时间特征的用户,简单地说,就是不想要。
PS
即使这 400 页可以“压缩一点”,我希望你不要指望我在 SO 上的几段中给出整个有意义的内容的摘要 ...
【讨论】:
这里有一个模型可以满足您的要求。
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 等列是严重错误。让我们按重要性排序。
这是一个严重的标准化错误。 "DateTimeTo" 很容易从下一行的单个 DateTime 导出;因此它是多余的,是重复的列。
实现 DateTo 列是 100% 的重复(下一行的 DateTime)。这需要 两倍的磁盘空间。对于一张大桌子,这将是非常不必要的浪费。
鉴于它是一个短行,每次访问时,您都需要 两倍的逻辑和物理 I/O 来读取该表。
并且缓存空间增加一倍(或者换句话说,任何给定的缓存空间只能容纳一半的行数)。
通过引入重复列,您已经引入了错误的可能性(该值现在可以通过两种方式得出:从重复的 DateTimeTo 列或下一行的 DateTimeFrom)。
这也是一个更新异常。当您更新任何 DateTimeFrom 为已更新时,必须获取上一行的 DateTimeTo(没什么大不了的,因为它很接近)和已更新(大不了,因为它是可以避免的附加动词)。
李>“更短”和“编码快捷方式”无关紧要,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 子查询以获取易于派生的数据要便宜得多。
【讨论】: