连接使用表值函数创建的表时优化 TSQL

Posted

技术标签:

【中文标题】连接使用表值函数创建的表时优化 TSQL【英文标题】:Optimize TSQL when joining a table created using a Table-Valued Function 【发布时间】:2021-04-18 23:26:28 【问题描述】:

以下代码运行不到一秒:

WITH GRIBS AS
(
    SELECT TOP 720 
        gfm.[ID],
        gfm.[FileName],
        gfm.[UTCDate] AS [TimeStep]
    FROM [hrdps].[GRIBFileMetrics]() gfm
    WHERE gfm.[Field] = 'WEARN_SFC_0'
    ORDER BY gfm.[UTCDate] DESC
)
SELECT
    b.[POIID],
    grib.[TimeStep],
    AVG(ISNULL(brain.[Amount], 0.0)) AS [Rainfall],
    grib.[FileName] 
FROM [storm].[BasinRain] brain
    LEFT JOIN [storm].[BasinGCSMap] bmap ON brain.[BasinGCSMapID] = bmap.[ID]
    LEFT JOIN [storm].[Basin] b ON b.[ID] = bmap.[BasinID]
    LEFT JOIN [storm].[POI] poi ON poi.[ID] = b.[POIID]
    FULL OUTER JOIN GRIBS grib ON brain.[TimeStep] = grib.[TimeStep]
GROUP BY b.[POIID], grib.[TimeStep], grib.[FileName]

创建 GRIBS 表涉及解析 TVF 中的大字符串,因此任何过滤器或加入它的速度都很慢。这就是我需要 FULL OUTER JOIN 的原因,因为即使没有 Brain.[TimeStep] 值,我也需要 grib.[TimeStep] 值,而反过来查询则需要永远运行。 COALESCE 工作正常,但随后我得到了 brain.[TimeStep] 值超出 grib.[TimeStep] 的范围,这也是不希望的,因为 GRIBS 查询的 TOP 720 ... ORDER BY gfm.[UTCDate] DESC 部分旨在将值限制为最后 30天....

所以我的问题是添加WHERE grib.[TimeStep] IS NOT NULLHAVING grib.[TimeStep] IS NOT NULL 会使上面的瞬间查询需要几分钟时间。

但我希望修改查询以排除 grib.[TimeStep] 为空的情况——显然无需在查询中添加任何时间——我总是可以在运行后过滤结果查询并且只添加几微秒,但这并不理想。有什么想法吗?

【问题讨论】:

OR 性能问题通常可以使用UNION ALL 来解决。 对于性能问题,您需要添加完整的表和索引定义,并通过brentozar.com/pastetheplan 共享查询计划请解释您理想中想要的确切连接逻辑(性能无法承受,因为我不是清楚。将GRIBS 转储到临时表并加入该表怎么样? @Charlieface 临时表的东西可能会起作用。如果我知道什么是表和索引定义以及如何添加它们,我可能会熟练地自己解决这个问题。如果没有人提出更清洁的解决方案,那么将在截止日期前解决这个 javascript 端并测试临时表的想法。 这只是一个带有CREATE TABLE...CREATE INDEX... 定义的脚本。在 SSMS 中,您可以右键单击表格并生成脚本。您还可以从 SSMS 获取查询计划。请执行edit 并详细说明所需的连接逻辑。顺便说一句,如果你也添加 TVF,它可能也可以优化。 临时表解决方案没什么不干净的,哈哈 【参考方案1】:

按照 Charlieface 在评论中的建议,将您的解决方案从 CTE 更改为临时表可能如下所示。

SELECT TOP 720 
    gfm.[ID],
    gfm.[FileName],
    gfm.[UTCDate] AS [TimeStep]
INTO #GRIBS
FROM [hrdps].[GRIBFileMetrics]() gfm
WHERE gfm.[Field] = 'WEARN_SFC_0'
ORDER BY gfm.[UTCDate] DESC;

SELECT
    b.[POIID],
    grib.[TimeStep],
    AVG(ISNULL(brain.[Amount], 0.0)) AS [Rainfall],
    grib.[FileName] 
FROM [storm].[BasinRain] brain
    LEFT JOIN [storm].[BasinGCSMap] bmap ON brain.[BasinGCSMapID] = bmap.[ID]
    LEFT JOIN [storm].[Basin] b ON b.[ID] = bmap.[BasinID]
    LEFT JOIN [storm].[POI] poi ON poi.[ID] = b.[POIID]
    FULL OUTER JOIN #GRIBS grib ON brain.[TimeStep] = grib.[TimeStep]
GROUP BY b.[POIID], grib.[TimeStep], grib.[FileName];

DROP TABLE #GRIBS;

把它放在一个存储过程中或使用整个东西即席。

【讨论】:

我也会索引临时表,TimeStep, ID 上的聚集键最有意义

以上是关于连接使用表值函数创建的表时优化 TSQL的主要内容,如果未能解决你的问题,请参考以下文章

涉及运算的表值参数问题

是否可以在 sqlkata 中查询参数化的表值函数?

sqlserver中的表值函数和标量值函数

如何在连接多个必须是 PIVOT 的表时创建临时表

循环遍历表值函数

使用实体框架的表值函数