有效地存储具有截然不同的键的键值对集

Pau*_*ott 9 schema database-design sql-server-2012 reporting

我继承了一个应用程序,它将许多不同类型的活动与一个站点相关联。大约有 100 种不同的活动类型,每一种都有不同的 3-10 个字段集。但是,所有活动都至少有一个日期字段(可以是日期、开始日期、结束日期、预定开始日期等的任意组合)和一个负责人字段。所有其他字段差异很大,开始日期字段不一定称为“开始日期”。

为每个活动类型制作一个子类型表将导致一个包含 100 个不同子类型表的模式,这将过于笨拙而无法处理。该问题的当前解决方案是将活动值存储为键值对。这是当前系统的一个大大简化的架构,可以理解这一点。

在此处输入图片说明

每个Activity有多个ActivityFields;每个 Site 有多个 Activity,SiteActivityData 表存储每个 SiteActivity 的 KVP。

这使得(基于 Web 的)应用程序非常容易编写代码,因为您真正需要做的就是遍历 SiteActivityData 中给定活动的记录,并为表单的每一行添加一个标签和输入控件。但是有很多问题:

  • 诚信不好;可以在 SiteActivityData 中放置一个不属于活动类型的字段,而 DataValue 是一个 varchar 字段,因此需要不断转换数字和日期。
  • 对这些数据进行报告和临时查询很困难、容易出错且速度缓慢。例如,获取结束日期在指定范围内的某种类型的所有活动的列表需要数据透视并将 varchars 转换为日期。报告作者讨厌这种模式,我不怪他们。

所以我正在寻找一种方法来存储大量几乎没有共同字段的活动,从而使报告更容易。到目前为止,我想出的是使用 XML 以伪 noSQL 格式存储活动数据:

在此处输入图片说明

Activity 表将包含每个活动的 XSD,从而无需 ActivityField 表。SiteActivity 将包含键值 XML,因此站点的每个活动现在都在一行中。

一个活动看起来像这样(但我还没有完全充实它):

<SomeActivityType>
  <SomeDateField type="StartDate">2000-01-01</SomeDateField>
  <AnotherDateField type="EndDate">2011-01-01</AnotherDateField>
  <EmployeeId type="ResponsiblePerson">1234</EmployeeId>
  <SomeTextField>blah blah</SomeTextField>
  ...
Run Code Online (Sandbox Code Playgroud)

好处:

  • XSD 将验证 XML,捕获错误,例如在数据库级别将字符串放入数字字段中,这对于将所有内容存储在 varchar 中的旧模式来说是不可能的。
  • 用于构建 Web 表单的 KVP 记录集可以很容易地使用 select ... from ActivityXML.nodes('/SomeActivityType/*') as T(r)
  • XML 的 xpath 子查询可用于生成包含开始日期、结束日期等列的结果集,而不使用数据透视表,例如 select ActivityXML.value('.[@type=StartDate]', 'datetime') as StartDate, ActivityXML.value('.[@type=EndDate]', 'datetime') as EndDate from SiteActivity where...

这看起来是个好主意吗?我想不出其他方法来存储如此大量不同的属性集。我的另一个想法是保留现有模式并将其转换为数据仓库中更易于查询的内容,但我以前从未设计过星型模式,也不知道从哪里开始。

附加问题:如果我使用 将标记定义为在 XSD 中具有日期数据类型xs:date,SQL Server 是否会将其索引为日期值?我担心如果我按日期查询,它需要将日期字符串转换为日期值并消除使用索引的任何机会。

Dav*_*ave 7

所以我正在寻找一种方法来存储大量几乎没有共同字段的活动,从而使报告更容易。

没有足够的代表先发表评论,所以我们走了!

如果主要目的是报告并且您有一个 DW(即使它不是星型模式),我建议尝试将其纳入星型模式。好处是快速、简单的查询。缺点是 ETL,但您已经在考虑将数据移动到新设计中,并且 ETL 星型架构可能比 XML 包装器解决方案更易于构建和维护(并且 SSIS 包含在您的 SQL Server 许可中)。此外,它还启动了公认的报告/分析设计过程。

那么如何做到这一点...听起来您拥有所谓的Factless Fact。这是定义没有关联度量(例如销售价格)的事件的属性的交集。您有部分或全部活动的日期吗?很可能你真的应该有一个活动、站点和日期的交集。

DimActivity- 我猜有一种模式,可以让您将它们分解为至少相对共享的列。如果是这样,你可能有三个?五?活动类别的维度。在最坏的情况下,您有几个一致的列,例如活动名称,您可以对其进行过滤,并为剩余的随机详细信息留下一般标题,例如“属性 1”等。

您不需要维度中的所有内容——活动维度中(可能)不应有任何日期——它们都应该在事实中,因为代理键引用了日期维度。例如,保留在人员维度中的日期将是出生日期,因为它是人员的属性。医院就诊日期将存在于事实中,因为它是与个人相关的时间点事件等,但它不是访问医院的人的属性。更多的日期讨论在事实上。

DimSite- 看起来很简单,所以我们将在这里描述代理键。本质上,这只是一个递增的唯一 ID。整数标识列很常见。这允许分离 DW 和源系统并确保数据仓库中的最佳连接。您的自然键或业务键通常会保留,但用于维护/设计而不是分析和连接。示例架构:

CREATE TABLE [DIM].[Site]
(
 SiteSK INT NOT NULL IDENTITY PRIMARY KEY
,SiteNK INT NOT NULL --source system key
,SiteName VARCHAR(500) NOT NULL
)
Run Code Online (Sandbox Code Playgroud)

DimDate- 日期属性。制作“智能钥匙”而不是身份。这意味着您可以为查询键入一个与日期相关的有意义的整数,例如 WHERE DateSK = 20150708。有许多免费脚本可以加载 DimDate,并且大多数都包含此智能密钥。(一种选择

DimEmployee - 如果对 DimPerson 进行更一般的更改,则您的 XML 包含此内容,并填充相关人员属性,因为它们可用且与报告相关。

而你的事实是:

FactActivitySite
DimSiteSK - FK to DimSite
DimActivitySK - FK to DimActivity
DimEmployee - FK to DimEmployee
DimDateSK - FK to DimDate
Run Code Online (Sandbox Code Playgroud)

您可以在事实中重命名这些,并且每个事件可以有多个日期键。事实通常非常大,因此避免更新通常很好...删除然后插入最新数据。

扩展到任何你需要你的事实日期: StartDateSK, EndDateSK, ScheduledStartDateSK

所有维度都应该有一个 Unknown 行,通常带有硬编码 -1 SK。当您加载事实,并且活动没有任何包含的日期时,它应该只加载 -1。

事实是对存储在维度中的属性的整数引用的集合,将它们连接在一起并以非常干净的连接模式获得所有详细信息,并且由于它的数据类型,事实非常小且快速。由于您在 SQL Server 中,添加列存储索引以进一步提高性能。您可以直接删除它并在 ETL 期间重建。使用 SQL 2014+ 后,您可以写入列存储索引。

在此处输入图片说明

如果你走这条路线研究维度建模。我会推荐Kimball 方法论。那里也有很多免费指南,但如果这不是一次性解决方案,那么投资可能是值得的。