【问题标题】:How can I increase the performance of scalar functions in SQL?如何提高 SQL 中标量函数的性能?
【发布时间】:2017-11-22 10:39:35
【问题描述】:

在我的表格视图中,我有 4 个要调用的标量函数。 例如:

CREATE view [SUMMARY]
AS 

SELECT  [Code] ,
    ...
    dbo.[udf_REPORTTYPE](ts.LegTankSystemId,ReportingDate,analysis.[AnalysisTypeName]) as ReportingType ,   
    dbo.[udf_WATER](ts.SystemId,LastObsDate) as WaterReading,
    case when analysis.LastObsDate is null then NULL else dbo.[udf_BJs_GENERAL](ts.LegTankSystemId,ISNULL(analysis.LastObsDate, ssd.ReportingDate)) end as [1Plus130_Reslut],
  (CASE  WHEN State ='NY' THEN dbo.[udf_NY](ts.SystemId,LastObsDate)
         WHEN State ='CT' THEN dbo.[udf_CT] (ts.SystemId,LastObsDate)
    END) AS State_SIR_Result
FROM LOBDW.SITE dimsite
LEFT JOIN [bjs].[udv_TANK] ts on dimsite.SiteId = ts.SiteId

...

在那些标量函数中,我读取相同的参数并进行不同的计算。

返回 400 行我的视图需要超过 1 分钟。这真的很糟糕。 如何提高这些标量函数的性能?如果两个函数都需要相同的数据,有没有办法从一个函数定义全局变量并在另一个函数中使用它?

例如函数;

CREATE FUNCTION [dbo].[udf_REPORTTYPE]
    (
      @TankSystemId int,
      @TimeStamp datetime2(7),
      @AnalysisTypeName varchar(10)
    )
RETURNS varchar(10)
AS 
BEGIN
    DECLARE @ReportType varchar(10);
    DECLARE @TimePeriod datetime2(7)
    DECLARE @LatestAnalysisDate datetime2(7)

    SELECT TOP 1 @TimePeriod = Date FROM udv_DailySiraData  where TankSystemId=@TankSystemId  ORDER BY Date DESC
    SELECT TOP 1 @LatestAnalysisDate = LastObsDate FROM [udv_ANALYSES] where TankSystemId =@TankSystemId  ORDER BY LastObsDate DESC

    SET @ReportType=@AnalysisTypeName
    IF ((@TimePeriod>=@TimeStamp) AND (@LatestAnalysisDate < @TimeStamp) AND @AnalysisTypeName IS NULL)
         SET @ReportType = 'No Analysis Result';
    IF ((@TimePeriod>=@TimeStamp) AND @AnalysisTypeName IS NULL)
         SET @ReportType = 'Latest';

    RETURN @ReportType;
END;

【问题讨论】:

  • 您在视图中使用的表中是否定义了任何 I 索引?
  • @MarkKram 还没有索引。但是我尝试优化我的函数并查看性能,然后尝试进行索引。如果两个函数都需要相同的数据,有没有办法从一个函数定义全局变量并在另一个函数中使用它?
  • 不要使用它们。考虑改用CTEs
  • @codeConcussion 任何示例如何用 CTE 替换函数?
  • 你的函数的问题是它们执行额外的查询,更糟糕的是,你每行执行另外 2-4 个函数(我想这也会执行额外的查询),并且由于函数被执行按 each 行,例如查询返回 100 多行,这意味着多 200-800 个 查询,这会大大降低性能。使用 CTE,您可以将所有逻辑放在一个查询中,然后加入该查询。另一种选择是使用APPLY @Squirrel 的答案。

标签: sql sql-server


【解决方案1】:

你可以将你的 UDF 转换为 OUTER APPLY ,像这样

SELECT  [Code] ,
    ...
    --dbo.[udf_REPORTTYPE](ts.LegTankSystemId,ReportingDate,analysis.[AnalysisTypeName]) as ReportingType ,   -- change from UDF to Outer Apply
    RT.ReportingType,
    dbo.[udf_WATER](ts.SystemId,LastObsDate) as WaterReading,
    case when analysis.LastObsDate is null then NULL else dbo.[udf_BJs_GENERAL](ts.LegTankSystemId,ISNULL(analysis.LastObsDate, ssd.ReportingDate)) end as [1Plus130_Reslut],
    (CASE  WHEN State ='NY' THEN dbo.[udf_NY](ts.SystemId,LastObsDate)
        WHEN State ='CT' THEN dbo.[udf_CT] (ts.SystemId,LastObsDate)
    END) AS State_SIR_Result
FROM    LOBDW.SITE dimsite
    LEFT JOIN [bjs].[udv_TANK] ts on dimsite.SiteId = ts.SiteId
    OUTER APPLY -- convert from udf_REPORTTYPE
    (
        SELECT  ReportingType = 
                CASE    WHEN    MAX(x.DATE)    >= ssd.ReportingDate 
                        AND     MAX(y.LastObsDate) <  ssd.ReportingDate 
                        AND     analysis.[AnalysisTypeName] iS NULL
                        THEN    'No Analysis Result'
                        WHEN    MAX(x.DATE)    >= ssd.ReportingDate 
                        AND     analysis.[AnalysisTypeName] iS NULL
                        THEN    'Latest'
                        ELSE    analysis.[AnalysisTypeName]
                        END
        FROM    udv_DailySiraData x
            INNER JOIN udv_ANALYSES y   ON  x.TankSystemId  = y.TankSystemId
        WHERE   x.TankSystemId  = ts.LegTankSystemId
    ) RT

【讨论】:

  • 感谢您,这大大提高了性能。我可以知道这个外部应用如何提高性能吗?
  • 如果我使用多个函数,我可以多次使用这个OuterApply,性能如何?例如: OUTER APPLY -- 从 udf_REPORTTYPE ( ) RT 转换 OUTER APPLY -- 从 udf_WATER ( ) WA 转换
【解决方案2】:

这是 OUTER APPLY 的 CTE 替代方案,尽管我确信它可以正常工作...

WITH TimePeriods AS (
    SELECT
        TankSystemId,
        TimePeriod = MAX(Date)
    FROM
        udv_DailySiraData
    GROUP BY
        TankSystemId
), AnalysisDates AS (
    SELECT
        TankSystemId,
        LatestAnalysisDate = MAX(LastObsDate)
    FROM
        udv_ANALYSES
    GROUP BY
        TankSystemId
)
SELECT
    ...
    ReportingType =
        CASE
            WHEN tp.TimePeriod >= ReportingDate AND ad.LatestAnalysisDate < ReportingDate AND analysis.AnalysisTypeName IS NULL THEN 'No Analysis Result'
            WHEN tp.TimePeriod >= ReportingDate AND analysis.AnalysisTypeName IS NULL THEN 'Latest'
            ELSE analysis.AnalysisTypeName
        END,
    ...
FROM
    LOBDW.SITE dimsite
LEFT JOIN
    bjs.udv_TANK ts
    ON dimsite.SiteId = ts.SiteId
LEFT JOIN
    TimePeriods tp
    ON ts.LegTankSystemId = tp.TankSystemId
LEFT JOIN
    AnalysisDates ad
    ON ts.LegTankSystemId = tp.TankSystemId

【讨论】:

  • 谢谢...我试试看性能。我尝试了松鼠提到的外部应用,它提高了性能。
猜你喜欢
  • 1970-01-01
  • 2014-03-20
  • 1970-01-01
  • 1970-01-01
  • 2016-10-12
  • 1970-01-01
  • 2014-01-04
  • 2021-07-03
  • 1970-01-01
相关资源
最近更新 更多