代码之家  ›  专栏  ›  技术社区  ›  Mike S

慢UDF的SQL替代方案

  •  0
  • Mike S  · 技术社区  · 8 年前

    我读过( Why is a UDF so much slower than a subquery? )但我正在努力解决/重写问题。

    根据以下@JasonALong的反馈,

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

    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 回复  |  直到 8 年前
        1
  •  3
  •   Jason A. Long    8 年前

    正如您在问题中提到的那样,标量函数和多语句表值函数(mTVF)对于优化器来说都是“黑匣子”。。。

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

    解决方案是重写有问题的函数。。。关键是#1,确保将它们重写为“内联表值函数”(iTVF)。优化器只会看到这些函数,就好像它们的代码直接输入到外部查询中一样(因此称为“内联”)。如果你不熟悉iTVFs,他们有两个要求。。。1它们必须是表函数(无论出于何种原因,MS仍然没有可用的标量版本)。。。和2这是最重要的。。。函数体必须是单个语句。

    好的是,web上不缺少关于创建“内联标量函数”的信息,使用编码为在web上返回标量值的表函数。

    希望这有帮助。。。

        2
  •  0
  •   Mike S    7 年前

    使用连接而不是“IN”子句帮助很大。(尽管我还将表var更改为临时表,这也有很大帮助。)