汇总多个子表

时间:2015-06-05 16:49:40

标签: sql sql-server

将一系列子表中的值汇总到SQL Server中的父表的最佳方法是什么?

我们说我们有合约表。此表有一系列子表,例如contract_timesheets,contract_materials,contract_other_expenses等。从这些子表中提取成本/小时/等并在父表中轻松访问它们的最佳方法是什么?

选项1:我的第一个想法是简单地使用视图。一个例子可能是这样的:

SELECT 
   contract_code, 
   caption, 
   description, 
   (
     SELECT SUM(t.hours * l.rate_hourly) 
     FROM timesheets t 
     JOIN labor l ON t.hr_code = l.hr_code AND t.contract_code = c.contract_code
   ) AS labor_cost, 
   ( 
     SELECT ...
   ) AS material_cost,
   ...
FROM contracts c

所以我们有一个可能有十几个或更多子查询的视图,其中很多都需要连接才能获取我们需要的所有信息。

这完全正常。直到我们有成千上万的行。然后事情变得明显变慢。它仍然可行,但行数增加得太高,或者服务器得到太多其他工作量,我担心这是不可行的。

是否有更有效的方法来构建这样的视图?

选项2 :另一个明显的解决方案是将这些数字滚动到父表中的物理字段中。最大的问题是在从各种客户端访问数据时保持数字。也许它是一份报告,也许它是一种形式,也许它是一些整合服务。因此,在显示报告/图表之前,尝试使用一些预制的汇总SQL文件作为事件在前端运行,这不是一个理想的解决方案。

为了确保汇总号码保持同步,我们可以将一系列触发器附加到所有子表(如果子表中的数字依赖于其他内容,则可能附加一些子表)。每次源数据更新时,我们都会将其汇总到父级。这似乎很麻烦,但如果触发器写得正确,我认为这样可以正常工作。

选项3 :在UI中执行所有操作。这也是一个选项,但是随着各种客户端访问数据,它会让事情变得不愉快。

选项4(?):由于大多数这些记录实际上已完成而无需添加更多数据,我还可以想象某种混合系统。父合同的基表将包含人工成本,材料成本等的物理列。当合同标记为已关闭(或某些其他状态指示不需要输入更多数据)时,将填写这些物理列(否则它们将为“空”)。然后,客户端可访问的视图可以根据状态(或仅仅是ISNULL stiuation)决定是直接从物理列返回数据,还是需要动态计算它。我不确定这会有什么表现,但值得一看。这意味着汇总数量最多只需计算几千行 - 其他一切都来自物理领域。

那么,这样做的正确方法是什么?我错过了其他可能性吗?

3 个答案:

答案 0 :(得分:1)

尝试使用Indexed View。这“实现”了观点。在视图上创建聚簇索引将允许您的查询直接转到索引而不是构成视图的所有基础表/查询。

编辑:修改了索引视图链接。

答案 1 :(得分:1)

我认为该视图可能是正确的答案,但是在SELECT列表中使用相关子查询编写查询的方式可能是导致行增加时性能下降的原因。如果您将所有内容作为与GROUP BY的连接编写,则可能允许查询优化器在执行时简单地为视图计划并为您提供更好的性能。

您是否也查看了Indexed Views?创建索引视图有很多限制,因此它们可能不适合您,但需要考虑的事项。从本质上讲,索引视图是一种非规范化。它将允许SQL Server在表中的基础数据发生更改时自动为您更新聚合。它当然可能会降低插入,更新和删除的性能,但如果聚合的性能至关重要,则需要考虑这一点。

答案 2 :(得分:0)

为了在这种情况下获得最佳的读取性能,索引视图是可行的方法。

CREATE VIEW labor_costs
WITH SCHEMA_BINDING
AS    
SELECT t.contract_code, t.hr_code,  SUM(t.hours * l.rate_hourly) AS labor_cost
FROM dbo.timesheets t 
GROUP BY t.contract_code, t.hr_code

GO
CREATE UNIQUE CLUSTERED INDEX UX_LaborCosts
ON LaborCosts(t.contract_code, t.hr_code)

获得索引视图后,您可以继续加入它。例如:

SELECT 
   c.contract_code, 
   c.caption, 
   c.description,
   lb.labor_cost

FROM 
   dbo.contracts c 
   LEFT JOIN dbo.labor_costs lb WITH (NOEXPAND)
     on c.contract_code = lb.contract_code AND c.hr_code = lb.hr_code