我们的数据库中有 12 种类型的费用,其中一些具有相当不同的数据减去 Amount 字段。我们在应用程序和报告中有多个地方需要单个和多个费用总计以及每个费用类型和总计的计数。最后,我们希望所有这些调用都使用一个视图,但对使用存储过程持开放态度。
为此,我们研究了多种替代方案,发现 CTE 允许我们在不使用临时表的情况下获取所有需要的数据。使用连接不起作用,因为无论我们尝试什么,我们都会看到记录被复制或删除。
我附上了费用表的子集和包含 CTE 的查询。有人有比这更好的选择吗?更快的东西?我们是否适当地接近这种“扁平化”?
请注意,无论是视图还是过程,此查询的执行计划都是相同的,而且过程似乎需要两倍的时间才能运行。
下面是代码
WITH pe AS
(
SELECT
EventRegistrationId
,sum(AmountPaid) as AmountPaidTotal
,sum(CommercialValueAmount) as CommercialValueAmountTotal
,count(1) as ExpenseCount
FROM PettyExpenses
WHERE IsDisputed = 0 AND IsUndisputed = 0
group by EventRegistrationId
),hpe AS
(
SELECT
EventRegistrationId
,sum(AmountPaid) as AmountPaidTotal
,sum(CommercialValueAmount) as CommercialValueAmountTotal
,count(1) as ExpenseCount
FROM HirePremisesExpenses
WHERE IsDisputed = 0 AND IsUndisputed = 0
group by EventRegistrationId
), ae AS
(
SELECT
EventRegistrationId
,sum(AmountPaid) as AmountPaidTotal
,sum(CommercialValueAmount) as CommercialValueAmountTotal
,count(1) as ExpenseCount
FROM AdvertisingExpenses
WHERE IsDisputed = 0 AND IsUndisputed = 0
group by EventRegistrationId
), se AS
(
SELECT
EventRegistrationId
,sum(AmountPaid) as AmountPaidTotal
,sum(CommercialValueAmount) as CommercialValueAmountTotal
,count(1) as ExpenseCount
FROM ServiceExpenses
WHERE IsDisputed = 0 AND IsUndisputed = 0
group by EventRegistrationId
), gse AS
(
SELECT
EventRegistrationId
,sum(AmountPaid) as AmountPaidTotal
,sum(CommercialValueAmount) as CommercialValueAmountTotal
,count(1) as ExpenseCount
FROM GoodsSuppliedExpenses
WHERE IsDisputed = 0 AND IsUndisputed = 0
group by EventRegistrationId
), thve AS
(
SELECT
EventRegistrationId
,sum(AmountPaid) as AmountPaidTotal
,sum(CommercialValueAmount) as CommercialValueAmountTotal
,count(1) as ExpenseCount
FROM TravelHireVehicleExpenses
WHERE IsDisputed = 0 AND
IsUndisputed = 0
group by EventRegistrationId
)
select
distinct eer.EventRegistrationId
--Petty Expense
,ISNULL(pe.AmountPaidTotal,0) as PettyExpenseAmountPaid
,ISNULL(pe.CommercialValueAmountTotal,0) as PettyExpenseCommercial
,ISNULL(pe.ExpenseCount,0) as PettyExpenseCount
--Hire On Premise Expense
,ISNULL(hpe.AmountPaidTotal,0) as HireOnPremisesExpenseAmountPaid
,ISNULL(hpe.CommercialValueAmountTotal,0) as HireOnPremisesExpenseCommercial
,ISNULL(hpe.ExpenseCount,0) as HireOnPremisesExpenseCount
--Advertising Expense
,ISNULL(ae.AmountPaidTotal,0) as AdvertisingExpenseAmountPaid
,ISNULL(ae.CommercialValueAmountTotal,0) as AdvertisingExpenseCommercial
,ISNULL(ae.ExpenseCount,0) as AdvertisingExpenseExpenseCount
--Services Expense
,ISNULL(se.AmountPaidTotal,0) as ServiceExpenseAmountPaid
,ISNULL(se.CommercialValueAmountTotal,0) as ServiceExpenseCommercial
,ISNULL(se.ExpenseCount,0) as ServiceExpenseExpenseCount
--Goods Supplied Expense
,ISNULL(gse.AmountPaidTotal,0) as GoodsSuppliedExpenseAmountPaid
,ISNULL(gse.CommercialValueAmountTotal,0) as GoodsSuppliedExpenseCommercial
,ISNULL(gse.ExpenseCount,0) as GoodsSuppliedExpenseExpenseCount
--Travel and Vehicle Expense
,ISNULL(thve.AmountPaidTotal,0) as TravelVehicleExpenseAmountPaid
,ISNULL(thve.CommercialValueAmountTotal,0) as TravelVehicleExpenseCommercial
,ISNULL(thve.ExpenseCount,0) as TravelVehicleExpenseExpenseCount
--All Expenses
,ISNULL(pe.AmountPaidTotal,0)
+ ISNULL(hpe.AmountPaidTotal,0)
+ ISNULL(ae.AmountPaidTotal,0)
+ ISNULL(se.AmountPaidTotal,0)
+ ISNULL(gse.AmountPaidTotal,0)
+ ISNULL(thve.AmountPaidTotal,0) as AllExpenseAmountPaidTotal
,ISNULL(pe.CommercialValueAmountTotal,0)
+ ISNULL(hpe.CommercialValueAmountTotal,0)
+ ISNULL(ae.CommercialValueAmountTotal,0)
+ ISNULL(se.CommercialValueAmountTotal,0)
+ ISNULL(gse.CommercialValueAmountTotal,0)
+ ISNULL(thve.CommercialValueAmountTotal,0) as AllExpenseCommercialValueTotal
,ISNULL(pe.ExpenseCount,0)
+ ISNULL(hpe.ExpenseCount,0)
+ ISNULL(ae.ExpenseCount,0)
+ ISNULL(se.ExpenseCount,0)
+ ISNULL(gse.ExpenseCount,0)
+ ISNULL(thve.ExpenseCount,0) as AllExpenseCount
from EventRegistrations eer
left join pe on pe.EventRegistrationId = eer.EventRegistrationId
left join hpe on hpe.EventRegistrationId = eer.EventRegistrationId
left join ae on ae.EventRegistrationId = eer.EventRegistrationId
left join se on se.EventRegistrationId = eer.EventRegistrationId
left join gse on gse.EventRegistrationId = eer.EventRegistrationId
left join thve on thve.EventRegistrationId = eer.EventRegistrationId
更新:
这是带有插入内容的数据库模式,供那些有兴趣观看它的人使用。
使用 SQL Server 2014 Standard,我将 db schema/inserts 更改为一个文件(此处为大文件),其中包含更多插入内容以及上传的执行计划和结果(2 个图像并排显示所有返回的列)
我用大量数据填充了数据库,并做了一些有趣的发现。
使用这些过滤索引在我的系统中返回结果大约需要 7 秒,而使用 Kin 建议的非过滤索引需要超过 1 分钟。
加上 Kin 推荐的索引(你可能已经有了,因为它似乎是表的主键):
事实证明,就流逝的时间而言,不存在明显的赢家。
您的查询形式使用
MERGE JOIN
s 获得了一个非常好的并行计划。使用
UNION ALL
s 的查询得到一个更简单的串行计划。就 IO 统计而言,串行计划似乎比并行计划更有效:
并行计划 IO 统计信息
串行计划 IO 统计信息
经过的时间非常相似,但并行计划的 CPU 时间更高。
请记住,过滤索引有一些限制,因此它们可能不是您的最佳选择。
根据您的需要,答案可能是将
CTE
示例中的每个转换为索引视图。这将以增加存储空间和稍微慢一点的 CRUD 操作为代价,节省每个查询的聚合。基本格式类似于:对每个 重复上面相同的模式
CTE
。然后你会VIEW
在你的例子中有一个 like 但你LEFT JOIN
到 newINDEXED VIEW
的而不是CTE
的。由于您提到您正在使用标准版,因此值得知道如果您希望索引视图的行为类似于索引视图而不是普通视图,则必须使用查询提示。
WITH (NOEXPAND)
顺便说一下,目前 Azure Sql Server 数据库也是如此。您的原始查询将对所有 6 个表进行表扫描。
您可以删除
distinct eer.EventRegistrationId
并使用GROUP BY eer.EventRegistrationId
,其余一切保持不变。以下索引将帮助您避免
TABLE SCAN
并会执行以下操作INDEX SEEK
:WITH
OPTION (MAXDOP 1)
+ 以上索引查询计划
统计IO输出
没有
OPTION (MAXDOP 1)
+ 以上索引查询计划:
统计IO输出
以下是完整性代码:
注意:您可以伪造行数和页数 <-- 仅出于教育原因