使用100个连接从规范化键值表创建非规范化表

时间:2014-11-21 22:54:34

标签: sql sql-server join etl olap

我有一个ETL过程,它从输入表中获取值,该输入表是一个键值表,每行都有一个字段ID,并将其转换为更加非规范化的表,其中每一行都包含所有值。具体来说,这是输入表:

StudentFieldValues (
    FieldId INT NOT NULL,
    StudentId INT NOT NULL,
    Day DATE NOT NULL,
    Value FLOAT NULL
)

FieldId是表Field中的外键,Day是表Days中的外键。 PK是前3个领域。目前有188个不同的领域。输出表格如下:

StudentDays (
    StudentId INT NOT NULL,
    Day DATE NOT NULL,
    NumberOfClasses FLOAT NULL,
    MinutesLateToSchool FLOAT NULL,
    ... -- the rest of the 188 fields
)

PK是前两个字段。

目前填充输出表的查询会自动加入StudentFieldValues 188次,每个字段一次。每个联接等同于StudentIdDay,并采用不同的FieldId。具体做法是:

SELECT Students.StudentId, Days.Day, 
       StudentFieldValues1.Value NumberOfClasses, 
       StudentFieldValues2.Value MinutesLateToSchool,
       ...
INTO StudentDays
FROM Students
CROSS JOIN Days
LEFT OUTER JOIN StudentFieldValues StudentFieldValues1 
ON Students.StudentId=StudentFieldValues1.StudentId AND 
   Days.Day=StudentFieldValues1.Day AND
   AND StudentFieldValues1.FieldId=1
LEFT OUTER JOIN StudentFieldValues StudentFieldValues2 
ON Students.StudentId=StudentFieldValues2.StudentId AND 
   Days.Day=StudentFieldValues2.Day AND 
   StudentFieldValues2.FieldId=2
... -- 188 joins with StudentFieldValues table, one for each FieldId

我担心这个系统不会扩展,因为更多的日子,学生和领域(特别是字段)被添加到系统中。已经有188个连接,我一直在阅读,如果你有一个查询数量的连接,你做错了什么。所以我基本上都在问:这件事很快会在我的脸上爆炸吗?有没有更好的方法来实现我想要做的事情?重要的是要注意这个查询记录最少,而且如果我一个接一个地添加字段,就不会有这样的事情。

更多详情:

  • MS SQL Server 2014,2x XEON E5 2690v2(20核,总共40个线程),128GB RAM。 Windows 2008R2。
  • 输入表中有3.52亿行,输出表中有1800万行 - 预计会随着时间的推移而增加。
  • 查询需要20分钟,我对此非常满意,但随着我添加更多字段,性能会下降。

3 个答案:

答案 0 :(得分:2)

是的,这会爆炸。你有"标准化的定义"和#34;非规范化"向后。字段/值表设计不是关系设计。它是设计的变体,有各种各样的问题。

我建议您不要尝试在SQL查询中透视数据。它不会那么好地扩展。 Instea,您需要将其作为一组行进行查询,因为它存储在数据库中,并将结果集提取到应用程序中。在那里你编写代码来逐行读取数据,并应用"字段"到对象的字段或散列图或其他东西。

答案 1 :(得分:2)

考虑使用条件聚合来做到这一点:

SELECT s.StudentId, d.Day, 
       max(case when sfv.FieldId = 1 then sfv.Value end) as NumberOfClasses, 
       max(case when sfv.FieldId = 2 then sfv.Value end) as MinutesLateToSchool,
       ...
INTO StudentDays
FROM Students s CROSS JOIN
     Days d LEFT OUTER JOIN
     StudentFieldValues sfv 
     ON s.StudentId = sfv.StudentId AND 
        d.Day = sfv.Day 
GROUP BY s.StudentId, d.Day;

这具有易于扩展的优点。您可以添加数百个字段,处理时间应与较少的字段相当(更长,但可比较)。添加新字段也更容易。

编辑:

此查询的更快版本将使用子查询而不是聚合:

SELECT s.StudentId, d.Day, 
       (SELECT TOP 1 sfv.Value FROM StudentFieldValues WHERE sfv.FieldId = 1 and sfv.StudentId = s.StudentId and sfv.Day = sfv.Day) as NumberOfClasses, 
        (SELECT TOP 1 sfv.Value FROM StudentFieldValues WHERE sfv.FieldId = 2 and sfv.StudentId = s.StudentId and sfv.Day = sfv.Day) as MinutesLateToSchool,
       ...
INTO StudentDays
FROM Students s CROSS JOIN
     Days d;

对于性能,您需要StudentFieldValues(StudentId, day, FieldId, Value)上的复合索引。

答案 2 :(得分:0)

我认为这里可能会有一些试验和错误,看看有什么用,但这里有一些你可以尝试的事情:

  • 在数据加载完成后禁用索引并重新启用
  • 禁用任何不需要在数据加载方案下运行的触发器。

以上内容取自msdn post,其中某人正在做与您相似的事情。

  • 考虑尝试仅根据已更改的记录更新去规范化表,如果可能的话。如果可能的话,限制结果集会更有效率。
  • 您可以尝试在代码(C#,vb等)中使用更多线程的迭代方法来构建此表,而您不会同时进行所有X连接。