Grouping on CASE statements = slow query?

  • estimated group by plan attached. Same problem here. execution with actual plan never finishes..

  • The most expensive operation by far is the sort before the aggregation. I'd like to see what happens if you force a HASH MATCH for the aggregation. Can you add OPTION (HASH GROUP) to the end of the query, see if that helps, and at least post the execution plan?

  • Here's a slightly different approach using a in-line table-valued function (itvf).

    I don't know if it will even compile since I have nothing to test against (your sample data require a Masterdata schema...).

    The concept in general: instead of using a scalar valued function wrapped by a multi-statement table-valued function I tried to convert it into an itvf. The first step should be to verify if this conversion returns the same results.

    The next change is to use a subquery (or cte, in this case) and apply the function to the reduced result.

    CREATE FUNCTION [Masterdata].[GetHierarchyPrefixRow_itvf]

    (

    -- Add the parameters for the function here

    @hierarchy_FK int,

    @hierarchyLevel smallint

    )

    RETURNS TABLE

    AS RETURN

    (

    WITH cte AS

    (SELECT

    CASE WHEN HL.ShowHierarchyPrefix = 1 THEN H.Prefix ELSE '' END

    + CASE WHEN HL.ShowHierarchyPrefix = 1 AND HL.ShowHierarchyLevelPrefix = 1 THEN '.' ELSE '' END

    + CASE WHEN HL.ShowHierarchyLevelPrefix = 1 THEN HL.Prefix ELSE '' END

    + CASE WHEN HL.ShowHierarchyLevelPrefix = 1 AND HL.ShowDimensionPrefix =1 THEN '.' ELSE '' END

    + CASE WHEN HL.ShowDimensionPrefix = 1 THEN D.Prefix ELSE '' END

    + CASE WHEN HL.ShowHierarchyPrefix = 1

    OR HL.ShowHierarchyLevelPrefix = 1

    OR HL.ShowDimensionPrefix = 1 THEN '.' ELSE '' END

    AS resultPrefix

    FROM Masterdata.Hierarchy H INNER JOIN

    Masterdata.HierarchyLevel HL ON H.Hierarchy_PK = HL.Hierarchy_FK LEFT OUTER JOIN

    Masterdata.Dimension D ON HL.Dimension_FK = D.Dimension_PK

    WHERE (H.Hierarchy_PK = @hierarchy_FK) AND (HL.[Level] = @hierarchyLevel)

    )

    SELECT

    ISNULL(resultPrefix,'Error Hierarchy level undefined') AS Prefix

    FROM cte

    )

    GO

    ;

    WITH cte AS

    (

    SELECT

    CASE WHEN y.TransactionType_FK IN (1, 5) THEN 'Act' ELSE 'Bud' END AS Scenario,

    CASE WHEN y.TransactionType_FK = 5 THEN 'YTD Journals Load' ELSE 'YTD Load' END AS [View],

    cp.ID AS cp_id,

    Year,

    Month,

    Period,

    d.FullID AS d_FullID,

    a.SourceSystem_FK,

    a.AccountNumber,

    pc.ID AS pc_id,

    p.ID AS p_id,

    cu.ID AS Currency,

    y.Currency_FK,

    y.ExchangeRateType_FK,

    SUM(Amount_YTD) AS Amount_YTD

    FROM

    Fact2012Base.PostingYTD AS y

    LEFT OUTER JOIN

    DimensionBase.Project AS p

    ON p.Project_FK = y.Project_FK

    INNER JOIN

    DimensionBase.Company AS pc

    ON p.OwnerCompany_FK = pc.Company_FK

    INNER JOIN

    DimensionBase.Department AS d

    ON y.Department_FK = d.Department_FK

    INNER JOIN

    DimensionBase.Account AS a

    ON y.Account_FK = a.Account_FK

    INNER JOIN

    DimensionBase.Company AS c

    ON y.Company_FK = c.Company_FK

    AND y.Currency_FK = c.Currency_FK

    INNER JOIN

    DimensionBase.Currency AS cu

    ON y.Currency_FK = cu.Currency_FK

    LEFT OUTER JOIN

    DimensionBase.Company AS cp

    ON y.CounterpartCompany_FK = cp.Company_FK

    WHERE

    (y.Year >= 2010)

    AND (y.TransactionVersion_FK <> 192)

    AND (y.SourceSystem_FK > 0)

    AND (y.TransactionType_FK IN (1, 2, 5))

    AND (c.Country_FK = 31)

    AND (y.Amount_YTD <> 0)

    )

    SELECT

    cte.*,

    pCountPartHier.Prefix + cp_id AS CounterPart,

    pEntHier.Prefix + d_FullID AS Entity,

    CASE WHEN cte.SourceSystem_FK = 13 THEN 'i' + pAccHier.Prefix ELSE pAccHier.Prefix + CASE WHEN LEFT(cte.AccountNumber, 1)

    IN ('3', '4') THEN c.ID + '_' ELSE '' END END + cte.AccountNumber AS Account,

    pProjHier.Prefix + pc_ID + '_' + p_ID AS Project

    FROM cte

    CROSS APPLY Masterdata.[GetHierarchyPrefixRow_itvf](10, 2) AS pCountPartHier

    CROSS APPLY Masterdata.[GetHierarchyPrefixRow_itvf](1, 3) AS pEntHier

    CROSS APPLY Masterdata.[GetHierarchyPrefixRow_itvf](3, 2) AS pAccHier

    CROSS APPLY Masterdata.[GetHierarchyPrefixRow_itvf](9, 1) AS pProjHier

    GROUP BY

    CASE WHEN cte.TransactionType_FK IN (1, 5) THEN 'Act' ELSE 'Bud' END,

    CASE WHEN cte.TransactionType_FK = 5 THEN 'YTD Journals Load' ELSE 'YTD Load' END,

    Year,

    Month,

    Period,

    pEntHier.Prefix + d_FullID,

    CASE WHEN SourceSystem_FK = 13 THEN 'i' + pAccHier.Prefix ELSE pAccHier.Prefix + CASE WHEN LEFT(AccountNumber, 1)

    IN ('3', '4') THEN c.ID + '_' ELSE '' END END + AccountNumber,

    pCountPartHier.Prefix + cp_ID,

    pProjHier.Prefix + pc_ID + '_' + p_ID,

    cu_ID,

    Currency_FK,

    ExchangeRateType_FK



    Lutz
    A pessimist is an optimist with experience.

    How to get fast answers to your question[/url]
    How to post performance related questions[/url]
    Links for Tally Table [/url] , Cross Tabs [/url] and Dynamic Cross Tabs [/url], Delimited Split Function[/url]

  • Have you looked at computed columns to render the contents of the CASE? They all look to be deterministic calculations, so you could even include the "post-calc" values in your indexing.

    ----------------------------------------------------------------------------------
    Your lack of planning does not constitute an emergency on my part...unless you're my manager...or a director and above...or a really loud-spoken end-user..All right - what was my emergency again?

  • LutzM (2/10/2012)


    Here's a slightly different approach using a in-line table-valued function (itvf).

    I don't know if it will even compile since I have nothing to test against (your sample data require a Masterdata schema...).

    The concept in general: instead of using a scalar valued function wrapped by a multi-statement table-valued function I tried to convert it into an itvf. The first step should be to verify if this conversion returns the same results.

    The next change is to use a subquery (or cte, in this case) and apply the function to the reduced result.

    CREATE FUNCTION [Masterdata].[GetHierarchyPrefixRow_itvf]

    (

    -- Add the parameters for the function here

    @hierarchy_FK int,

    @hierarchyLevel smallint

    )

    RETURNS TABLE

    AS RETURN

    (

    WITH cte AS

    (SELECT

    CASE WHEN HL.ShowHierarchyPrefix = 1 THEN H.Prefix ELSE '' END

    + CASE WHEN HL.ShowHierarchyPrefix = 1 AND HL.ShowHierarchyLevelPrefix = 1 THEN '.' ELSE '' END

    + CASE WHEN HL.ShowHierarchyLevelPrefix = 1 THEN HL.Prefix ELSE '' END

    + CASE WHEN HL.ShowHierarchyLevelPrefix = 1 AND HL.ShowDimensionPrefix =1 THEN '.' ELSE '' END

    + CASE WHEN HL.ShowDimensionPrefix = 1 THEN D.Prefix ELSE '' END

    + CASE WHEN HL.ShowHierarchyPrefix = 1

    OR HL.ShowHierarchyLevelPrefix = 1

    OR HL.ShowDimensionPrefix = 1 THEN '.' ELSE '' END

    AS resultPrefix

    FROM Masterdata.Hierarchy H INNER JOIN

    Masterdata.HierarchyLevel HL ON H.Hierarchy_PK = HL.Hierarchy_FK LEFT OUTER JOIN

    Masterdata.Dimension D ON HL.Dimension_FK = D.Dimension_PK

    WHERE (H.Hierarchy_PK = @hierarchy_FK) AND (HL.[Level] = @hierarchyLevel)

    )

    SELECT

    ISNULL(resultPrefix,'Error Hierarchy level undefined') AS Prefix

    FROM cte

    )

    GO

    ;

    WITH cte AS

    (

    SELECT

    CASE WHEN y.TransactionType_FK IN (1, 5) THEN 'Act' ELSE 'Bud' END AS Scenario,

    CASE WHEN y.TransactionType_FK = 5 THEN 'YTD Journals Load' ELSE 'YTD Load' END AS [View],

    cp.ID AS cp_id,

    Year,

    Month,

    Period,

    d.FullID AS d_FullID,

    a.SourceSystem_FK,

    a.AccountNumber,

    pc.ID AS pc_id,

    p.ID AS p_id,

    cu.ID AS Currency,

    y.Currency_FK,

    y.ExchangeRateType_FK,

    SUM(Amount_YTD) AS Amount_YTD

    FROM

    Fact2012Base.PostingYTD AS y

    LEFT OUTER JOIN

    DimensionBase.Project AS p

    ON p.Project_FK = y.Project_FK

    INNER JOIN

    DimensionBase.Company AS pc

    ON p.OwnerCompany_FK = pc.Company_FK

    INNER JOIN

    DimensionBase.Department AS d

    ON y.Department_FK = d.Department_FK

    INNER JOIN

    DimensionBase.Account AS a

    ON y.Account_FK = a.Account_FK

    INNER JOIN

    DimensionBase.Company AS c

    ON y.Company_FK = c.Company_FK

    AND y.Currency_FK = c.Currency_FK

    INNER JOIN

    DimensionBase.Currency AS cu

    ON y.Currency_FK = cu.Currency_FK

    LEFT OUTER JOIN

    DimensionBase.Company AS cp

    ON y.CounterpartCompany_FK = cp.Company_FK

    WHERE

    (y.Year >= 2010)

    AND (y.TransactionVersion_FK <> 192)

    AND (y.SourceSystem_FK > 0)

    AND (y.TransactionType_FK IN (1, 2, 5))

    AND (c.Country_FK = 31)

    AND (y.Amount_YTD <> 0)

    )

    SELECT

    cte.*,

    pCountPartHier.Prefix + cp_id AS CounterPart,

    pEntHier.Prefix + d_FullID AS Entity,

    CASE WHEN cte.SourceSystem_FK = 13 THEN 'i' + pAccHier.Prefix ELSE pAccHier.Prefix + CASE WHEN LEFT(cte.AccountNumber, 1)

    IN ('3', '4') THEN c.ID + '_' ELSE '' END END + cte.AccountNumber AS Account,

    pProjHier.Prefix + pc_ID + '_' + p_ID AS Project

    FROM cte

    CROSS APPLY Masterdata.[GetHierarchyPrefixRow_itvf](10, 2) AS pCountPartHier

    CROSS APPLY Masterdata.[GetHierarchyPrefixRow_itvf](1, 3) AS pEntHier

    CROSS APPLY Masterdata.[GetHierarchyPrefixRow_itvf](3, 2) AS pAccHier

    CROSS APPLY Masterdata.[GetHierarchyPrefixRow_itvf](9, 1) AS pProjHier

    GROUP BY

    CASE WHEN cte.TransactionType_FK IN (1, 5) THEN 'Act' ELSE 'Bud' END,

    CASE WHEN cte.TransactionType_FK = 5 THEN 'YTD Journals Load' ELSE 'YTD Load' END,

    Year,

    Month,

    Period,

    pEntHier.Prefix + d_FullID,

    CASE WHEN SourceSystem_FK = 13 THEN 'i' + pAccHier.Prefix ELSE pAccHier.Prefix + CASE WHEN LEFT(AccountNumber, 1)

    IN ('3', '4') THEN c.ID + '_' ELSE '' END END + AccountNumber,

    pCountPartHier.Prefix + cp_ID,

    pProjHier.Prefix + pc_ID + '_' + p_ID,

    cu_ID,

    Currency_FK,

    ExchangeRateType_FK

    Thank you very much LutzM!

    I did not get the time to look into before now. But in the mean time the number of results return by the original query was doubled and the execution-time went up to aprox 5 mins.

    I tried your approach and it improved things quite a lot! From 5 mins to 1 min. So thats a great improvment!

    Attached is the actual execution-plan after implementing LutzM solution. I think i will accept this one as the final solution for now 🙂

  • Looking at the execution plan it seems like the statistics for PostingYTD are "slightly" off (estimated 43k rows vs. actual 2.000k rows).

    Is there any index maintenance performed regulary?



    Lutz
    A pessimist is an optimist with experience.

    How to get fast answers to your question[/url]
    How to post performance related questions[/url]
    Links for Tally Table [/url] , Cross Tabs [/url] and Dynamic Cross Tabs [/url], Delimited Split Function[/url]

Viewing 6 posts - 16 through 20 (of 20 total)

You must be logged in to reply to this topic. Login to reply