0

具有两个UDT参数的查询需要0.3秒,但是当封装在内联表值函数中时,需要3.5+秒。慢UDF的SQL替代

我读过(Why is a UDF so much slower than a subquery?),但我正在努力修复/重写。

每低于@ JasonALong的反馈,

的SELECT语句中0.3秒完成执行计划:https://www.brentozar.com/pastetheplan/?id=HJnrqC53Z(注意SQL可在此页)。

代码功能是完成了这个链接下面和执行计划粘贴3.5秒:https://www.brentozar.com/pastetheplan/?id=BJZbqR93b

SELECT 
SelectedContracts.MeasurableID, 
SelectedContracts.EntityID, 

EntityName, 
EntityAbbrev, 
EntityLogoURL, 
EntityHex1, 
EntityHex2, 
EntitySportID, 

MeasurableName, 
MeasurableOrganizationID, 
YearFilter, 
SeasonFilter, 
CategoryFilter, 
ResultFilter, 
Logo4Result, 
MeasurableSportID, 
MouseoverFooter, 
ContractRank4Org, 
ContractEndUTC, 

HighContractPrice4Period, 
HighTradeID, 
HighTradeUTC, 
HighTradeNumberOfContracts, 
HighTradeCurrency, 

LowContractPrice4Period, 
LowTradeID, 
LowTradeUTC, 
LowTradeNumberOfContracts, 
LowTradeCurrency, 

LastTradePrice, 
LastTradeID, 
LastTradeUTC, 
LastTradeNumberOfContracts, 
LastTradeCurrency, 

SecondLastTradePrice, 
SecondLastTradeID, 
SecondLastTradeUTC, 
SecondLastTradeNumberOfContracts, 
SecondLastTradeCurrency, 

ContractPrice4ChangeCalc, 
ContractID4ChangeCalc, 
ContractUTC4ChangeCalc, 
ContractsNumberTraded4ChangeCalc, 
ContractCurrency4ChangeCalc, 

HighestBidID, 
HighestBidMemberID, 
HighestBidPrice, 
HighestBidAvailableContracts, 
HighestBidCurrency, 

LowestAskID, 
LowestAskMemberID, 
LowestAskPrice, 
LowestAskAvailableContracts, 
LowestAskCurrency 


FROM 
(
    SELECT 
     dbo.Contracts.MeasurableID, 
     dbo.Contracts.EntityID 
    FROM 
     dbo.Contracts 
    WHERE 
     dbo.Contracts.MeasurableID IN ((2030),(2017),(2018),(2019),(2020),(2028),(2024),(2027),(2029),(2022),(4018),(4019),(4020),(4021)) 
    GROUP BY 
     dbo.Contracts.MeasurableID, 
     dbo.Contracts.EntityID 
) SelectedContracts 


INNER JOIN 
(
    SELECT 
     dbo.Entities.ID, 
     --dbo.Entities.OrganizationID, -- Get OrganizationID from Measurable since some Entities (European soccer teams) have multiple Orgs 
     dbo.Entities.EntityName, 
     dbo.Entities.EntityAbbrev, 
     dbo.Entities.logoURL AS EntityLogoURL, 
     dbo.Entities.Hex1 AS EntityHex1, 
     dbo.Entities.Hex2 AS EntityHex2, 
     dbo.Entities.SportID AS EntitySportID 
    FROM 
     dbo.Entities 
) SelectedEntities ON SelectedContracts.EntityID = SelectedEntities.ID 


INNER JOIN 
(
    SELECT 
     dbo.Measurables.ID AS MeasurableID, 
     dbo.Measurables.Name AS MeasurableName, 
     dbo.Measurables.OrganizationID AS MeasurableOrganizationID, 
     dbo.Measurables.[Year] AS YearFilter, 
     dbo.Measurables.Season AS SeasonFilter, 
     dbo.Measurables.Category AS CategoryFilter, 
     dbo.Measurables.Result AS ResultFilter, 
     dbo.Measurables.Logo4Result, 
     dbo.Measurables.SportID AS MeasurableSportID, 
     dbo.Measurables.MouseoverFooter, 
     dbo.Measurables.ContractRank4Org, 
     dbo.Measurables.EndUTC AS ContractEndUTC 
    FROM 
     dbo.Measurables 
) MEASURABLES_table ON SelectedContracts.MeasurableID = MEASURABLES_table.MeasurableID 


LEFT JOIN 
(
    SELECT 
     MeasurableID, 
     EntityID, 
     ContractPrice AS HighContractPrice4Period, 
     ID AS HighTradeID, 
     UTCMatched AS HighTradeUTC, 
     NumberOfContracts AS HighTradeNumberOfContracts, 
     CurrencyCode AS HighTradeCurrency 
    FROM 
       (
        SELECT 
         *, ROW_NUMBER() OVER (
          PARTITION BY MeasurableID, 
          EntityID 
         ORDER BY 
          ContractPrice DESC, 
          ID DESC 
         ) RowNumber -- ID DESC means most recent trade of ties 
        FROM 
         Contracts 
        WHERE 
         MeasurableID IN ((2030),(2017),(2018),(2019),(2020),(2028),(2024),(2027),(2029),(2022),(4018),(4019),(4020),(4021)) 
         AND dbo.Contracts.UTCmatched < DATEADD(DAY, -30, SYSDATETIME()) 
         AND (   CurrencyCode IN (('GBP'), ('CAD'), ('INR'), ('BRL'), ('MXN'), ('CHF'), ('RUB')) 
           ) 
       ) AS InnerSelect4HighTrade 

    WHERE 
     InnerSelect4HighTrade.RowNumber = 1 

) HighTrades ON SelectedContracts.MeasurableID = HighTrades.MeasurableID AND SelectedContracts.EntityID = HighTrades.EntityID 


LEFT JOIN 
(
    SELECT 
     MeasurableID, 
     EntityID, 
     ContractPrice AS LowContractPrice4Period, 
     ID AS LowTradeID, 
     UTCMatched AS LowTradeUTC, 
     NumberOfContracts AS LowTradeNumberOfContracts, 
     CurrencyCode AS LowTradeCurrency 
    FROM 
     (
      SELECT 
        *, ROW_NUMBER() OVER (
        PARTITION BY MeasurableID, 
        EntityID 
       ORDER BY 
        ContractPrice ASC, 
        ID DESC 
       ) RowNumber -- ID DESC means most recent trade of ties 
      FROM 
       Contracts 
      WHERE 
       MeasurableID IN ((2030),(2017),(2018),(2019),(2020),(2028),(2024),(2027),(2029),(2022),(4018),(4019),(4020),(4021)) 
       AND dbo.Contracts.UTCmatched < DATEADD(DAY, -30, SYSDATETIME()) 
       AND (   CurrencyCode IN (('GBP'), ('CAD'), ('INR'), ('BRL'), ('MXN'), ('CHF'), ('RUB')) 
         )   
     ) AS InnerSelect4LowTrade 

    WHERE  InnerSelect4LowTrade.RowNumber = 1 

) LowTrades ON SelectedContracts.MeasurableID = LowTrades.MeasurableID AND SelectedContracts.EntityID = LowTrades.EntityID 


LEFT JOIN 
(
    SELECT 
     MeasurableID, 
     EntityID, 
     ContractPrice AS LastTradePrice, 
     ID AS LastTradeID, 
     UTCMatched AS LastTradeUTC, 
     NumberOfContracts AS LastTradeNumberOfContracts, 
     CurrencyCode AS LastTradeCurrency 
    FROM 
     (
      SELECT 
       *, ROW_NUMBER() OVER (
        PARTITION BY MeasurableID, 
        EntityID 
       ORDER BY 
        ID DESC 
       ) RowNumber -- ID DESC means most recent trade of ties 
      FROM 
       Contracts 
      WHERE 
       MeasurableID IN ((2030),(2017),(2018),(2019),(2020),(2028),(2024),(2027),(2029),(2022),(4018),(4019),(4020),(4021)) 
       AND (   CurrencyCode IN (('GBP'), ('CAD'), ('INR'), ('BRL'), ('MXN'), ('CHF'), ('RUB')) 
         ) 
     ) AS InnerSelect4LastTrade 

    WHERE InnerSelect4LastTrade.RowNumber = 1 

) LastTrades ON SelectedContracts.MeasurableID = LastTrades.MeasurableID AND SelectedContracts.EntityID = LastTrades.EntityID 


LEFT JOIN 
(
    SELECT 
     MeasurableID, 
     EntityID, 
     ContractPrice AS SecondLastTradePrice, 
     ID AS SecondLastTradeID, 
     UTCMatched AS SecondLastTradeUTC, 
     NumberOfContracts AS SecondLastTradeNumberOfContracts, 
     CurrencyCode AS SecondLastTradeCurrency 
    FROM 
     (
      SELECT 
       *, ROW_NUMBER() OVER (
        PARTITION BY MeasurableID, 
        EntityID 
       ORDER BY 
        ID DESC 
       ) RowNumber -- ID DESC means most recent trade of ties 
      FROM 
       Contracts 
      WHERE 
       MeasurableID IN ((2030),(2017),(2018),(2019),(2020),(2028),(2024),(2027),(2029),(2022),(4018),(4019),(4020),(4021)) 
       AND (   CurrencyCode IN (('GBP'), ('CAD'), ('INR'), ('BRL'), ('MXN'), ('CHF'), ('RUB')) 
         ) 
--need time filter??? 
     ) AS InnerSelect4SecondToLastTrade 

    WHERE InnerSelect4SecondToLastTrade.RowNumber = 2 

) SecondToLastTrade ON SelectedContracts.MeasurableID = SecondToLastTrade.MeasurableID AND SelectedContracts.EntityID = SecondToLastTrade.EntityID 


LEFT JOIN 
(
    SELECT 
     MeasurableID, 
     EntityID, 
     ContractPrice AS ContractPrice4ChangeCalc, 
     ID AS ContractID4ChangeCalc, 
     UTCMatched AS ContractUTC4ChangeCalc, 
     NumberOfContracts AS ContractsNumberTraded4ChangeCalc, 
     CurrencyCode AS ContractCurrency4ChangeCalc 
    FROM 
     (
      SELECT 
       *, ROW_NUMBER() OVER (
        PARTITION BY MeasurableID, 
        EntityID 
       ORDER BY 
        ID DESC -- ID DESC equals the most recent trade if ties 
       ) RowNumber 
      FROM 
       Contracts 
      WHERE 
       MeasurableID IN ((2030),(2017),(2018),(2019),(2020),(2028),(2024),(2027),(2029),(2022),(4018),(4019),(4020),(4021)) 
       AND (   CurrencyCode IN (('GBP'), ('CAD'), ('INR'), ('BRL'), ('MXN'), ('CHF'), ('RUB')) 
         ) 
      AND dbo.Contracts.UTCmatched < DATEADD(Day ,-30, SYSDATETIME()) 
     ) AS InnerSelect4ChangeCalcPerPeriod 

    WHERE InnerSelect4ChangeCalcPerPeriod.RowNumber = 1 

) Trade4ChangeCalcPerPeriod ON SelectedContracts.MeasurableID = Trade4ChangeCalcPerPeriod.MeasurableID AND SelectedContracts.EntityID = Trade4ChangeCalcPerPeriod.EntityID 


LEFT JOIN 
(
    SELECT 
     MeasurableID, 
     EntityID, 
     ID AS HighestBidID, 
     MemberID AS HighestBidMemberID, 
     BidPrice AS HighestBidPrice, 
     AvailableContracts AS HighestBidAvailableContracts, 
     CurrencyCode AS HighestBidCurrency 
    FROM 
     (
      SELECT 
       *, ROW_NUMBER() OVER (
        PARTITION BY MeasurableID, 
        EntityID 
       ORDER BY 
        BidPrice DESC, 
        ID DESC 
       ) RowNumber 
      FROM 
       dbo.Interest2Buy 
      WHERE 
       MeasurableID IN ((2030),(2017),(2018),(2019),(2020),(2028),(2024),(2027),(2029),(2022),(4018),(4019),(4020),(4021)) 
      AND AvailableContracts > 0 
       AND (   CurrencyCode IN (('GBP'), ('CAD'), ('INR'), ('BRL'), ('MXN'), ('CHF'), ('RUB')) 
         ) 
     ) AS InnerSelect4HighestBid 

    WHERE InnerSelect4HighestBid.RowNumber = 1 

) HighestBids ON SelectedContracts.MeasurableID = HighestBids.MeasurableID AND SelectedContracts.EntityID = HighestBids.EntityID 


LEFT JOIN 
(
    SELECT 
     MeasurableID, 
     EntityID, 
     ID AS LowestAskID, 
     MemberID AS LowestAskMemberID, 
     AskPrice AS LowestAskPrice, 
     AvailableContracts AS LowestAskAvailableContracts, 
     CurrencyCode AS LowestAskCurrency 
    FROM 
     (
      SELECT 
       *, ROW_NUMBER() OVER (
        PARTITION BY MeasurableID, 
        EntityID 
       ORDER BY 
        AskPrice ASC, 
        ID DESC 
       ) RowNumber 
      FROM 
       dbo.Interest2Sell 
      WHERE 
       MeasurableID IN ((2030),(2017),(2018),(2019),(2020),(2028),(2024),(2027),(2029),(2022),(4018),(4019),(4020),(4021)) 
       AND AvailableContracts > 0 
       AND (   CurrencyCode IN (('GBP'), ('CAD'), ('INR'), ('BRL'), ('MXN'), ('CHF'), ('RUB')) 
         ) 
     ) AS InnerSelect4BestAsk 

    WHERE InnerSelect4BestAsk.RowNumber = 1 

) BestAsks ON SelectedContracts.MeasurableID = BestAsks.MeasurableID AND SelectedContracts.EntityID = BestAsks.EntityID 
+2

这太含糊不清,你的UDF可能是标量函数或表值,它们可能是单语句或多语句,你可以将它们用作相关的子查询或加入它们。该名单继续下去。你需要给出一个与你的特定情况相关的实际例子,也许在阅读完之后? https://stackoverflow.com/help/mcve *(为两个版本的代码获取执行计划以查看它们的不同之处也不失为一个好主意,这可能有助于找出它们之间的差异) * – MatBailie

+0

对于沿着fyi的任何读者,我尝试添加Option(重新编译)https:// stackoverflow。com/questions/20864934/option-recompile-always-always-faster-why,但这没有什么区别。还试图在.Net(web服务器)中创建SQL查询并直接运行,但事实证明这比使用函数或存储过程要慢。 –

+1

如果使用UDT运行它,但未包含在函数中会发生什么?这将帮助您隔离导致性能问题的更改(添加UDT或封装在函数中)。我怀疑那是UDT是这个问题。如果是这样,请尝试重写查询以加入游戏而不是使用IN(),或者将索引和统计信息应用于UDT。 – MatBailie

回答

0

使用联接,而不是“IN”的条款了大量帮助。 (尽管我也将表var改为临时表,并且这对表格也有很大的帮助)

2

标量函数和木里语句表值函数(mTVF)的,因为在你的问题中提到,“黑匣子”给优化器...

所以,我想问的是,“为什么这么糟糕?”。答案是,为了提出一个尽可能高效的好计划,它需要知道有关特定需求的某些细节以及它将从中提取数据的表的信息(这就是为什么过时的静态数据可以严重影响性能)。所以...当你使用标量函数或mTVF时,优化器无法像使用内联代码一样评估所有需求。它的回应是简单地假设该函数只执行一次,并根据该假设制定计划。

由于假设是错误的,错误的计划会生成并最终导致可怕的表现。

解决方案是重写有问题的函数......关键是要#1,确保将它们重写为“内联表值函数”(iTVF)。这些是优化器将看到的唯一函数,就好像它们的代码直接输入到外部查询中一样(因此术语“内联”)。如果你对iTVFs不熟悉,他们有2个要求...... 1他们必须是表格功能(无论什么原因,MS STILL没有可用的标量版本)......并且...... 2这是biggie ...函数体必须是单个语句。

那么,如果你不需要一个表值函数,你需要一个标量函数?那么没有什么说多值函数不能返回单个(标量)值......这就是为什么那些知道所有函数的代码都是iTVF的情况的原因。

好的一点是,网络上不缺少关于创建“内联标量函数”的信息,使用表函数进行编码以返回Web上的标量值。

希望这有助于...

+0

@Jasonalong谢谢,你的信息让我指向更好的研究。然而,就你的评论的底部而言,我的问题是我想返回一个表格而不是标量。 (此外,我不认为有可能将没有JOINS的数据返回到子查询中......) –

+1

很难说没有任何细节......但是......就这样就没有混淆......当我说, “作为一个单一的陈述”,这不应该被解释为“简单陈述”...... CTE,派生表和子查询都完全可以接受。它只是意味着你不能使用IF块来添加控制流或者声明和设置内部变量。这里有一个简单的方法来思考它...... SQL语句应该用分号终止......如果你能在主体中添加多个分号,SQL Server会认为它是多值的。 –

+1

如果想要验证SQL Server是否将“内联”函数视为特定函数,只需查询sys.objects表。 SELECT * FROM sys.objects o WHERE o.name = N'tfn_SomeFunction'; ...或者...你可以一次查看所有的uds ... SELECT * FROM sys.objects o WHERE o.type IN('FN','FS','IF','TF'); –