0

UDT 매개 변수가 두 개인 쿼리는 0.3 초가 걸리지 만 인라인 테이블 값 함수에 캡슐화되면 3.5 초가 걸립니다.느린 UDF에 대한 SQL 대안

나는 (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

이것은 너무 애매합니다. UDF는 스칼라 함수 또는 테이블 값일 수 있으며, 단일 명령문 또는 다중 명령문이 될 수 있습니다. 상관 하위 쿼리 또는 합류 하위 쿼리로 사용할 수 있습니다. 목록은 계속 켜져 있습니다. 이 상황을 읽은 후 특정 상황과 관련된 실제 사례를 제시해야합니다. https://stackoverflow.com/help/mcve * (코드의 두 버전에 대한 실행 계획을 통해 차이점을 확인하는 것은 좋지 않을 것입니다. 이는 차이점을 찾는 데 도움이 될 수 있습니다) * – MatBailie

+0

fyi 따라 오는 모든 독자 들어, 옵션 (재 컴파일) https : // stackoverflow 추가 시도했다.com/questions/20864934/option-recompile-is-always-faster-why 그러나 이것은 차이를 만들지 않았습니다. 또한 시도했다 .Net (웹 서버)에서 SQL 쿼리를 작성하고 직접 실행하지만 이것은 심지어 함수 또는 저장된 프로 시저를 사용하는 것보다 느린 증명. –

+1

UDT로 실행했지만 함수로 래핑되지 않으면 어떻게됩니까? 이는 성능 문제 (UDT 추가 또는 함수 랩핑)를 야기한 변경 사항을 분리하는 데 도움이됩니다. 그게 문제가되는 UDT라고 생각합니다. 그렇다면'IN()'을 사용하는 대신에 게임에 참여하도록 쿼리를 재 작성하거나 UDT에 인덱스와 통계를 적용 해보십시오. – MatBailie

답변

0

"IN"절 대신에 조인을 사용하면 많은 도움이됩니다. (비록 테이블 var를 임시 테이블로 변경 했는데도 큰 도움이되었습니다.)

2

스칼라 함수와 muli 문 테이블 반환 함수 (mTVF) 모두가이 링크에서 아래 및 실시 계획 ​​붙여 3.5 초에 완료 기능에 대한

코드 , "블랙 박스"를 옵티마이 저가 ...

그래서 "나는 왜 이렇게 나쁘지?"라는 질문을 던집니다. 대답은 가능한 한 효율적으로 실행되는 좋은 계획을 제시하기 위해 데이터를 가져올 테이블에 대한 정보뿐만 아니라 특정 요구 사항에 대한 세부 정보를 알아야한다는 것입니다. 성능에 심각한 영향을 미침). 그래서 ... 스칼라 함수 나 mTVF를 사용할 때 옵티마이 저는 인라인 코드로 모든 요구 사항을 평가할 수 없습니다. 응답은 단순히 함수가 한 번만 실행하고 해당 가정에 따라 계획을 작성한다고 가정하는 것입니다.

가정이 잘못되었으므로 잘못된 계획이 생성되어 끔찍한 성능으로 끝납니다.

해결책은 문제가되는 기능을 다시 작성하는 것입니다. 핵심은 # 1로, "인라인 테이블 값 함수"(iTVF)로 다시 작성해야합니다. 이들은 옵티마이 저가 자신의 코드가 외부 쿼리 (즉, "인라인"이라는 용어)에 직접 입력 된 것처럼 볼 수있는 유일한 함수입니다. iTVF에 익숙하지 않다면, 그들은 2 가지 요구 사항을 가지고 있습니다 ... 1 그들은 테이블 함수 여야 만합니다. (어떤 이유로 든 MS STILL은 스칼라 버전을 사용할 수 없습니다) ... 그리고 ... 2 ... biggie ... 함수 본문은 단일 문이어야합니다.

그렇다면 테이블 값 함수가 필요하지 않으면 스칼라 함수가 필요합니까? 음, 다중 값 함수가 단일 (스칼라) 값을 반환 할 수 없다는 것은 아무것도 없습니다 ... 따라서 iTVF와 같은 모든 함수를 코드화하여 상황을 인식하는 사람들이 있습니다.

좋은 점은 웹에서 스칼라 값을 반환하도록 코딩 된 테이블 함수를 사용하여 "인라인 스칼라 함수"를 만드는 것에 대한 정보가 부족하다는 것입니다.

희망이 도움이 ...

+0

@ JasonALong 감사합니다. 귀하의 정보는 더 나은 연구를 지향하고 있습니다. 그러나 귀하의 의견의 하단 부분과 관련하여, 내 문제는 내가 스칼라가 아닌 테이블을 반환하려는 것입니다. (또한 조인없이 탐색 된 데이터를 하위 쿼리로 반환 할 수 있다고 생각하지 않습니다.) –

+1

구체적인 내용없이 말하기는 어렵지만 ... 혼동이 없습니다 ... 내가 말했을 때, "단일 문장으로", "간단한 문장"으로 해석해서는 안됩니다 ... CTE, 파생 테이블 및 하위 쿼리는 모두 완벽하게 수용 할 수 있습니다. 이는 단순히 IF 블록을 사용하여 제어 흐름을 추가하거나 내부 변수를 선언하고 설정하는 것과 같은 일을 할 수 없다는 것을 의미합니다. 여기에 대해 생각해 볼 수있는 쉬운 방법이 있습니다 ... SQL 문은 세미콜론으로 끝나야합니다 ... 본문에 세미콜론을 두 개 이상 추가 할 수 있으면 SQL Server는 다중 값으로 간주합니다. –

+1

SQL Server가 특정 함수를 "인라인"으로 간주하는지 확인하려면 sys.objects 테이블을 쿼리하면됩니다. SELECT * FROM sys.objects o 어디에서 o.name = N'tfn_SomeFunction '; ... 또는 ... 한번에 모든 ud를 볼 수 있습니다 ... SELECT * FROM sys.objects o o.type IN ('FN', 'FS', 'IF', 'TF'); –