cte performance issue

  • Hi

    This code has been written by a developer and it seems to be causing an issue performance wise.

    It is taking over 2 minutes to run in SSMS - and when run from the app it times out.

    We cannot use temp tables either because the 3rd party vendor doesn't allow it for some reason.

    Looking at the execution plan I see that an Index seek with estimated number of rows = 1302 and Actual number of rows = 6066879, yet when I look on the table in question all the statistics where updated last night. So why the huge discrepancy in the actual and estimated ?

    here is the query:

    ET DATEFORMAT DMY;

    WITH WP AS

    (

    SELECT

    EWP.EMPLOY_REF

    ,WP.PATTNDATE

    ,WP.[HOURS] AS [HOURS]

    FROM

    [dbo].[WORKPATN] WP

    INNER JOIN dbo.EMPLOYEE_WORKPATTERN EWP ON EWP.WK_PATTN = WP.WORKPATTERN AND GETDATE() BETWEEN EWP.WORKPATTERN_START AND EWP.WORKPATTERN_END

    )

    ,WPO AS

    (

    SELECT

    WEEK_ENDING

    ,EMPLOY_REF

    ,SUM(MO) AS MO

    ,SUM(TU) AS TU

    ,SUM(WE) AS WE

    ,SUM(TH) AS TH

    ,SUM(FR) AS FR

    ,SUM(SA) AS SA

    ,SUM(SU) AS SU

    FROM

    (

    SELECT DISTINCT

    CAST(TW.WEEK_ENDING AS [DATE]) AS WEEK_ENDING

    ,TH.EMPLOY_REF

    ,CASE WHEN WO.[DATE] = COALESCE(TW.WEEK_ENDING -6, WP.PATTNDATE -6) THEN COALESCE(WO.[HOURS], WP.[HOURS]) ELSE 0.0 END AS MO

    ,CASE WHEN WO.[DATE] = COALESCE(TW.WEEK_ENDING -5, WP.PATTNDATE -5) THEN COALESCE(WO.[HOURS], WP.[HOURS]) ELSE 0.0 END AS TU

    ,CASE WHEN WO.[DATE] = COALESCE(TW.WEEK_ENDING -4, WP.PATTNDATE -4) THEN COALESCE(WO.[HOURS], WP.[HOURS]) ELSE 0.0 END AS WE

    ,CASE WHEN WO.[DATE] = COALESCE(TW.WEEK_ENDING -3, WP.PATTNDATE -3) THEN COALESCE(WO.[HOURS], WP.[HOURS]) ELSE 0.0 END AS TH

    ,CASE WHEN WO.[DATE] = COALESCE(TW.WEEK_ENDING -2, WP.PATTNDATE -2) THEN COALESCE(WO.[HOURS], WP.[HOURS]) ELSE 0.0 END AS FR

    ,CASE WHEN WO.[DATE] = COALESCE(TW.WEEK_ENDING -1, WP.PATTNDATE -1) THEN COALESCE(WO.[HOURS], WP.[HOURS]) ELSE 0.0 END AS SA

    ,CASE WHEN WO.[DATE] = COALESCE(TW.WEEK_ENDING, WP.PATTNDATE) THEN COALESCE(WO.[HOURS], WP.[HOURS]) ELSE 0.0 END AS SU

    FROM

    dbo.TIMESHEET_HEADER TH

    INNER JOIN dbo.TIMESHEET_WEEK TW ON TW.TIMESHEET_WEEK_REF = TH.TIMESHEET_WEEK_REF

    LEFT JOIN dbo.WORKPATTERN_OVERRIDE WO ON WO.EMPLOY_REF = TH.EMPLOY_REF

    LEFT JOIN WP ON WP.EMPLOY_REF = WO.EMPLOY_REF

    GROUP BY TW.WEEK_ENDING,TH.EMPLOY_REF,WO.[DATE],WO.[HOURS],WP.PATTNDATE,WP.[HOURS]

    )AS T

    GROUP BY T.WEEK_ENDING, T.EMPLOY_REF

    )

    SELECT

    T.District

    ,T.Shop

    ,T.[LBO Shop Code]

    ,T.[Timesheet Group]

    ,T.[Week Ending]

    ,T.EMPLOY_REF

    ,T.[Employee Name]

    ,SUM(T.[Monday Hours]) AS [Monday Hours]

    ,SUM(T.[Tuesday Hours]) AS [Tuesday Hours]

    ,SUM(T.[Wednesday Hours]) AS [Wednesday Hours]

    ,SUM(T.[Thursday Hours]) AS [Thursday Hours]

    ,SUM(T.[Friday Hours]) AS [Friday Hours]

    ,SUM(T.[Saturday Hours]) AS [Saturday Hours]

    ,SUM(T.[Sunday Hours]) AS [Sunday Hours]

    ,SUM(T.[Week Hours]) AS [Week Hours]

    ,SUM(T.[Days Taken]) AS [Days Taken]

    FROM

    (

    SELECT

    dist.[DESCRIPTION] AS [District]

    ,shop.BRANCH AS [Shop]

    ,shop.LOC_REF AS [LBO Shop Code]

    ,tg.[DESCRIPTION] AS [Timesheet Group]

    ,tw.WEEK_ENDING AS [Week Ending]

    ,emp.EMPLOY_REF

    ,COALESCE(emp.PREFERRED_NAME,emp.FORENAME) + ' ' + emp.SURNAME AS [Employee Name]

    ,SUM(tl.MONDAY_HOURS) AS [Monday Hours]

    ,SUM(tl.TUESDAY_HOURS) AS [Tuesday Hours]

    ,SUM(tl.WEDNESDAY_HOURS) AS [Wednesday Hours]

    ,SUM(tl.THURSDAY_HOURS) AS [Thursday Hours]

    ,SUM(tl.FRIDAY_HOURS) AS [Friday Hours]

    ,SUM(tl.SATURDAY_HOURS) AS [Saturday Hours]

    ,SUM(tl.SUNDAY_HOURS) AS [Sunday Hours]

    ,SUM(tl.WEEK_HOURS) AS [Week Hours]

    ,CAST(

    CASE WHEN tl.MONDAY_HOURS <> 0 AND W.MO <> 0 THEN tl.MONDAY_HOURS / W.MO ELSE 0 END

    + CASE WHEN tl.TUESDAY_HOURS <> 0 AND W.TU <> 0 THEN tl.TUESDAY_HOURS / W.TU ELSE 0 END

    + CASE WHEN tl.WEDNESDAY_HOURS <> 0 AND W.WE <> 0 THEN tl.WEDNESDAY_HOURS / W.WE ELSE 0 END

    + CASE WHEN tl.THURSDAY_HOURS <> 0 AND W.TH <> 0 THEN tl.THURSDAY_HOURS / W.TH ELSE 0 END

    + CASE WHEN tl.FRIDAY_HOURS <> 0 AND W.FR <> 0 THEN tl.FRIDAY_HOURS / W.FR ELSE 0 END

    + CASE WHEN tl.SATURDAY_HOURS <> 0 AND W.SA <> 0 THEN tl.SATURDAY_HOURS / W.SA ELSE 0 END

    + CASE WHEN tl.SUNDAY_HOURS <> 0 AND W.SU <> 0 THEN tl.SUNDAY_HOURS / W.SU ELSE 0 END

    AS decimal(5,2))

    AS [Days Taken]

    FROM

    dbo.TIMESHEET_LINE tl

    INNER JOIN dbo.TIMESHEET_HEADER th ON tl.timesheet_header_ref = th.timesheet_header_ref

    LEFT JOIN dbo.TIMESHEET_ABSENCE ta ON tl.TIMESHEET_HEADER_REF = ta.TIMESHEET_HEADER_REF AND tl.TIMESHEET_ABSENCE_REF = ta.TIMESHEET_ABSENCE_REF

    LEFT JOIN dbo.EMPLOYEE_TABLE emp ON th.EMPLOY_REF = emp.EMPLOY_REF

    LEFT JOIN dbo.TIMESHEET_GROUP tg ON th.TIMESHEET_GROUP_REF = tg.TIMESHEET_GROUP_REF

    INNER JOIN dbo.TIMESHEET_WEEK tw ON tw.timesheet_week_ref = th.timesheet_week_ref

    LEFT JOIN dbo.TIMESHEET_GROUP_INPUT_TYPE tgit ON tgit.timesheet_group_input_type_ref = tl.timesheet_group_input_type_ref

    LEFT JOIN dbo.V_DEPT dist ON emp.DEPARTMENT = dist.CODE

    LEFT JOIN dbo.LOCATION shop ON emp.LOCATION = shop.LOC_REF

    INNER JOIN WPO W ON W.EMPLOY_REF = th.EMPLOY_REF AND tw.WEEK_ENDING = W.WEEK_ENDING

    WHERE

    tl.TIMESHEET_GROUP_INPUT_TYPE_REF = 0

    AND

    ta.ABSENCE_TYPE = 'HOLIDAY'

    GROUP BY

    dist.[DESCRIPTION]

    ,shop.BRANCH

    ,shop.LOC_REF

    ,tg.[DESCRIPTION]

    ,tw.WEEK_ENDING

    ,emp.EMPLOY_REF

    ,COALESCE(emp.PREFERRED_NAME,emp.FORENAME) + ' ' + emp.SURNAME

    ,tl.MONDAY_HOURS

    ,tl.TUESDAY_HOURS

    ,tl.WEDNESDAY_HOURS

    ,tl.THURSDAY_HOURS

    ,tl.FRIDAY_HOURS

    ,tl.SATURDAY_HOURS

    ,tl.SUNDAY_HOURS

    ,W.MO

    ,W.TU

    ,W.WE

    ,W.TH

    ,W.FR

    ,W.SA

    ,W.SU

    ) AS T

    GROUP BY

    T.District,

    T.Shop,

    T.[LBO Shop Code],

    T.[Timesheet Group],

    T.[Week Ending],

    T.EMPLOY_REF,

    T.[Employee Name]

    order by T.EMPLOY_REF

  • Table definitions, index definitions and execution plan please?

    Gail Shaw
    Microsoft Certified Master: SQL Server, MVP, M.Sc (Comp Sci)
    SQL In The Wild: Discussions on DB performance with occasional diversions into recoverability

    We walk in the dark places no others will enter
    We stand on the bridge and no one may pass
  • Complex queries run the risk of failing to obtain a satisfactory execution plan, as the optimiser doesn't have sufficient time to explore all possibilities. You'll get a plan so the query will run but it won't be a good one. The property sheet of the leftmost

    operator in the execution plan will tell you if this is the case. IMHO it should be a warning, it's too significant to be hidden away.

    The more tables you have in your query, the more possibilities there are for joining them. Your query references at least 14. Any query referencing more than 7 or 8 tables should be checked for optimiser timeout.

    It's a pity you can't use #temp tables, that's a helluva limitation. It would take minutes to make this query work properly. Having said that, it would benefit from a revisit anyway. Why is this table included?

    LEFT JOIN dbo.TIMESHEET_GROUP_INPUT_TYPE tgit ON tgit.timesheet_group_input_type_ref = tl.timesheet_group_input_type_ref.

    CTE's have encouraged lazy coding practice. You're likely to find other quick wins.

    “Write the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

    For fast, accurate and documented assistance in answering your questions, please read this article.
    Understanding and using APPLY, (I) and (II) Paul White
    Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden

  • Not sure if i have done correctly - attempting to attach the execution plan !!:-)

    A

  • @Gail - Ive added the execution plan just now.

    the table is defined as such :

    USE [databaseX]

    GO

    /****** Object: Table [dbo].[WORKPATN] Script Date: 14/11/2014 15:37:40 ******/

    SET ANSI_NULLS ON

    GO

    SET QUOTED_IDENTIFIER ON

    GO

    SET ANSI_PADDING OFF

    GO

    CREATE TABLE [dbo].[WORKPATN](

    [WORKPATN_REF] [INT] IDENTITY(1,1) NOT NULL,

    [WORKPATTERN] [VARCHAR](10) NOT NULL,

    [HOURS] [DECIMAL](19, 4) NULL,

    [PATTNDATE] [DATETIME] NULL,

    [OT_RATE] [DECIMAL](19, 4) NULL,

    [BANKHOLIDAY] [BIT] NOT NULL,

    CONSTRAINT [PK_WORKPATN] PRIMARY KEY CLUSTERED

    (

    [WORKPATN_REF] ASC

    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 99) ON [PRIMARY]

    ) ON [PRIMARY]

    GO

    SET ANSI_PADDING OFF

    GO

    ALTER TABLE [dbo].[WORKPATN] ADD CONSTRAINT [DF_WORKPATN_HOURS] DEFAULT (0.0) FOR [HOURS]

    GO

    ALTER TABLE [dbo].[WORKPATN] ADD CONSTRAINT [DF_WORKPATN_OT_RATE] DEFAULT (0.0) FOR [OT_RATE]

    GO

    ALTER TABLE [dbo].[WORKPATN] ADD DEFAULT (0) FOR [BANKHOLIDAY]

    GO

    The indexes are scripted as such:

    USE [Databasex]

    GO

    /****** Object: Index [WORKPATTERN_DATE] Script Date: 14/11/2014 15:40:24 ******/

    CREATE UNIQUE NONCLUSTERED INDEX [WORKPATTERN_DATE] ON [dbo].[WORKPATN]

    (

    [WORKPATTERN] ASC,

    [PATTNDATE] ASC

    )

    INCLUDE ( [HOURS],

    [OT_RATE],

    [BANKHOLIDAY]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 99) ON [PRIMARY]

    GO

    USE [databasex]

    GO

    /****** Object: Index [WORKPATTERN] Script Date: 14/11/2014 15:40:17 ******/

    CREATE NONCLUSTERED INDEX [WORKPATTERN] ON [dbo].[WORKPATN]

    (

    [WORKPATTERN] ASC

    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 99) ON [PRIMARY]

    GO

    USE [databasex]

    GO

    /****** Object: Index [PK_WORKPATN] Script Date: 14/11/2014 15:40:09 ******/

    ALTER TABLE [dbo].[WORKPATN] ADD CONSTRAINT [PK_WORKPATN] PRIMARY KEY CLUSTERED

    (

    [WORKPATN_REF] ASC

    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 99) ON [PRIMARY]

    GO

    USE [databasex]

    GO

    /****** Object: Index [PATTNDATE] Script Date: 14/11/2014 15:39:52 ******/

    CREATE NONCLUSTERED INDEX [PATTNDATE] ON [dbo].[WORKPATN]

    (

    [PATTNDATE] ASC

    )

    INCLUDE ( [WORKPATTERN],

    [HOURS]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 99) ON [PRIMARY]

    GO

  • PearlJammer1 (11/14/2014)


    We cannot use temp tables either because the 3rd party vendor doesn't allow it for some reason.

    Heh... I'm always amazed but such things. What they don't realize is that purposefully not using Temp Tables can cause system spawned Temp Tables to just explode. It's frequently much better to use "Divide'n'Conquer" methods to build smaller Temp Tables and join to those than to let the system build massive work tables to support such things has Hash Joins or trying to resolve accidental Many-to-Many joins that require a UNIQUE or GROUP BY somewhere in the code to prevent duplicated rows.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • The query plan is timing out, and I'm not surprised!

    Take this one simple looking expression in the WPO CTE:

    CASE WHEN WO.[DATE] = COALESCE(TW.WEEK_ENDING -6, WP.PATTNDATE -6) THEN COALESCE(WO.[HOURS], WP.[HOURS]) ELSE 0.0 END AS MO

    It translates as this - see comments inside for immediately obvious areas to investigate

    [Expr1033] = Scalar Operator(

    case

    --== WO.DATE can be NULL - if the data doesn't allow for NULLS,

    --== change the join to an INNER JOIN

    WHEN [SnowdropKCSHR2011_Train].[dbo].[WORKPATTERN_OVERRIDE].[DATE] as [WO].[DATE]=

    CASE

    --== TIMESHEET_WEEK is on an INNER JOIN,

    --== so WEEK_ENDING probably is never NULL

    --== unless you have NULLS in that column,

    --== so this logic may be redundant

    WHEN ([SnowdropKCSHR2011_Train].[dbo].[TIMESHEET_WEEK].[WEEK_ENDING] as [TW].[WEEK_ENDING]-'1900-01-07 00:00:00.000') IS NOT NULL

    THEN [SnowdropKCSHR2011_Train].[dbo].[TIMESHEET_WEEK].[WEEK_ENDING] as [TW].[WEEK_ENDING]-'1900-01-07 00:00:00.000'

    ELSE [Expr1030]-'1900-01-07 00:00:00.000'

    END

    THEN

    --== BOTH WORKPATTERN_OVERRIDE and "WP" are OUTER JOINS,

    --== yet there is no allowance for this,

    --== so you could still return a NULL for any day

    CASE

    WHEN [SnowdropKCSHR2011_Train].[dbo].[WORKPATTERN_OVERRIDE].[HOURS] as [WO].[HOURS] IS NOT NULL

    THEN CONVERT_IMPLICIT(decimal(19,4),[SnowdropKCSHR2011_Train].[dbo].[WORKPATTERN_OVERRIDE].[HOURS] as [WO].[HOURS],0)

    ELSE [Expr1031]

    END

    ELSE (0.0000)

    END),

    Now, whether this whole query is even correct comes into question.

    It needs a code review - think about what you are trying to achieve in terms of sets,

    Maybe draw a quick VENN diagram to help visualise the relationships and intersections required

    This can help to understand the right way to join the tables.

    MM



    select geometry::STGeomFromWKB(0x0106000000020000000103000000010000000B0000001000000000000840000000000000003DD8CCCCCCCCCC0840000000000000003DD8CCCCCCCCCC08408014AE47E17AFC3F040000000000104000CDCCCCCCCCEC3F9C999999999913408014AE47E17AFC3F9C99999999991340000000000000003D0000000000001440000000000000003D000000000000144000000000000000400400000000001040000000000000F03F100000000000084000000000000000401000000000000840000000000000003D0103000000010000000B000000000000000000143D000000000000003D009E99999999B93F000000000000003D009E99999999B93F8014AE47E17AFC3F400000000000F03F00CDCCCCCCCCEC3FA06666666666FE3F8014AE47E17AFC3FA06666666666FE3F000000000000003D1800000000000040000000000000003D18000000000000400000000000000040400000000000F03F000000000000F03F000000000000143D0000000000000040000000000000143D000000000000003D, 0);

  • Forum Etiquette: How to post Reporting Services problems
  • [/url]
  • Forum Etiquette: How to post data/code on a forum to get the best help - by Jeff Moden
  • [/url]
  • How to Post Performance Problems - by Gail Shaw
  • [/url]

Viewing 7 posts - 1 through 7 (of 7 total)

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