Tuning a Stored Procedure

  • Hello,

    I have a procedure that was taking around 11-12 seconds to run on SQL Server 2012 Enterprise edition with Always ON configured..

    There is table named fs.performance that has 21995968 records and it is referenced a couple of times in the procedure.

    I analyzed the procedure in DETA and it suggested a couple of indexes on table fs.performance. I created those indexes and the longest this procedure takes to run (tested with different parameters) is around 5 seconds now.

    This procedure gets called from application using different parameters. It runs fast for some parameters and slow for some other. Please suggest some ways to tune this procedure so that I get the results within under a second

    Attached are the details.

    Thanks in advance.

  • I took a quick review of the sproc. I lost count of the things that are suboptimal in it, and that doesn't take into account anything about the data structures themselves.

    The complexity of this goes WAY beyond free help on a forum, at least for me. Fully optimizing this entire sproc and it's data structures could be 10+ hours of time, and I don't do that for free. 😎

    Best,
    Kevin G. Boles
    SQL Server Consultant
    SQL MVP 2007-2012
    TheSQLGuru on googles mail service

  • I'll support what Kevin mentioned.

    There's a great room for improvement and I'm sure it wouldn't stop with this procedure as the whole database will have similar problems.

    I'd suggest that you hire some expert that won't only help you to improve performance, but will also teach you how to prevent future performance problems.

    Luis C.
    General Disclaimer:
    Are you seriously taking the advice and code from someone from the internet without testing it? Do you at least understand it? Or can it easily kill your server?

    How to post data/code on a forum to get the best help: Option 1 / Option 2
  • Further on Kevin's and Luis's answers, optimizing this code to half the execution time should not be too hare, neither getting under one second but this takes some time, do you think is worth the effort? How many times a day is this code executed?

    😎

  • Yogeshwar Phull (8/5/2015)


    Hello,

    I have a procedure that was taking around 11-12 seconds to run on SQL Server 2012 Enterprise edition with Always ON configured..

    There is table named fs.performance that has 21995968 records and it is referenced a couple of times in the procedure.

    I analyzed the procedure in DETA and it suggested a couple of indexes on table fs.performance. I created those indexes and the longest this procedure takes to run (tested with different parameters) is around 5 seconds now.

    This procedure gets called from application using different parameters. It runs fast for some parameters and slow for some other. Please suggest some ways to tune this procedure so that I get the results within under a second

    Attached are the details.

    Thanks in advance.

    To perhaps assist others in helping you, I've at least "prettified" the code to make it more readable, and eliminated the cursor, as it doesn't appear to be the least bit necessary. Here's the updated code, with the procedure part commented out (so that some of SSMS's ability to syntax check can function better while I was prettifying). and also the cursor commented out and it's replacement immediately following:

    --CREATE procedure [rpi].[udf_GetPerformance_FS] (

    DECLARE @LipperIdList varchar(MAX) = ''

    , @FundsOnly char(1) = '0'-- really a bit but FundNets can't do that. Default 0 in CASE not passed

    , @LanguageId varchar(10) = ''

    , @WebsiteId varchar(10) = ''

    , @RangeId varchar(10) = '';

    --)

    --AS

    --BEGIN

    --FundGroupId will be NULL IF providing a LipperId list, only populated IF using triplet

    DECLARE @LipperId AS TABLE (

    LipperId int,

    Idx INT IDENTITY(1,1)

    );

    DECLARE @BaseCurrency char(3), @BaseCurrencyName varchar(100);

    DECLARE @latestrptPerfReportDate date;-- we will always return the latest data, but the FS tables will accumulate multiple ReportDates

    SELECT @latestrptPerfReportDate = MAX(reportdate)

    FROM fs.performance

    WHERE ReportDate < DATEADD(day, -14, GETDATE())

    SELECT @languageid = CASE WHEN ISNUMERIC(@languageid) = 1 THEN @languageid ELSE NULL END

    SELECT @websiteid = CASE WHEN ISNUMERIC(@websiteid) = 1 THEN @websiteid ELSE NULL END

    SELECT @rangeid = CASE WHEN ISNUMERIC(@rangeid) = 1 THEN @rangeid ELSE NULL END

    /*

    DECLARE @RetailWebsite int

    SELECT @RetailWebsite = id FROM rpt.website WHERE name = 'Retail'

    */

    -- start BY inserting based ON @LipperIdList

    INSERT INTO @LipperId (LipperId)

    SELECT String

    FROM app.udf_SplitString(@LipperIdList, ',')

    ORDER BY idx;-- we will respect the ORDER they were sent in

    IF @WebsiteId IS NOT NULL -- sufficient to test just one of the three parameters to see IF we are using this approach

    BEGIN

    INSERT INTO @LipperId (LipperId)

    SELECT fl.LipperId

    FROM [rpi].[udf_FC_GetFundList] (@LanguageId, @WebsiteId, @RangeId, NULL /* spotlight */, NULL /* sandprating */) AS fl

    ORDER BY RangeName, fundname, shareclass;

    /*

    FROM [rpi].[udf_GetFundList_Brief] (@LanguageId, @WebsiteId, @RangeId) AS fl

    INNER JOIN rpt.portfolioattributes AS pa

    ON fl.fundid = pa.fundid

    */

    END

    --/* Not everyone in our list may have the same date values available, so we take the worst, to ensure all funds have something to display

    --*/

    IF @FundsOnly = 1

    -- we throw out anything that isn't a fund-type lipperid. We do this indirectly AS we don't have a fundtype/securitytype to rely upON AND these are also in app.fund

    BEGIN

    DELETE

    FROM @LipperId

    WHERE Lipperid IN ( -- automatically DISTINCT

    SELECT LipperBenchmarkId

    FROM rpt.portfolioattributes

    UNION

    SELECT lippersectorid

    FROM rpt.portfolioattributes

    );

    END

    CREATE TABLE #results(

    idx int,

    RowType varchar(10),-- 'Fund' / 'BM', 'S'

    FundGroupId int,-- will be SET with an UPDATE statement later

    FundId int,-- fundId (FT's app.Fund.Id )

    fsFundId int,

    Primary_Share_Class varchar(3),

    LipperId int,

    CurrencyCode char(3),

    InceptionDate date,

    ISIN varchar(12),

    FundName varchar(100),

    FundShortName varchar(100),

    ShareClass varchar(100),

    Returns_1mth decimal(20,10),

    Returns_3mth decimal(20,10),

    Returns_6mth decimal(20,10),

    Returns_1Yr decimal(20,10),

    Returns_3Yr decimal(20,10),

    Returns_5Yr decimal(20,10),

    Minus1 decimal(20,10),

    Minus2 decimal(20,10),

    Minus3 decimal(20,10),

    Minus4 decimal(20,10),

    YTD decimal(20,10),

    PCT1Y0_MTH decimal(20,10),

    PCT1Y1_MTH decimal(20,10),

    PCT1Y2_MTH decimal(20,10),

    PCT1Y3_MTH decimal(20,10),

    PCT1Y4_MTH decimal(20,10)

    );

    -- main select

    INSERT INTO #results (

    RowType ,

    FundGroupId ,

    FundId,

    Primary_Share_Class,

    LipperId ,

    CurrencyCode ,

    InceptionDate ,

    ISIN,

    ShareClass,

    Returns_1mth ,

    Returns_3mth ,

    Returns_6mth ,

    Returns_1Yr ,

    Returns_3Yr,

    Returns_5Yr ,

    Minus1 ,

    Minus2 ,

    Minus3 ,

    Minus4 ,

    YTD ,

    PCT1Y0_MTH ,

    PCT1Y1_MTH ,

    PCT1Y2_MTH,

    PCT1Y3_MTH ,

    PCT1Y4_MTH )

    SELECT

    'Fund' AS RowType,-- 'Fund' in the CASE of these records.

    NULL AS FundGroupId,-- will be SET with an UPDATE statement later, needed here AS SELECT into

    f.Id AS FundId,-- fundId (FT's app.Fund.Id )

    pa.Primary_Share_Class,

    l.LipperId,

    f.CurrencyId AS CurrencyCode,

    pa.InceptionDate,

    f.AccountGr AS ISIN,

    pa.ShareClass,

    Returns_1mth,

    Returns_3mth,

    Returns_6mth,

    Returns_1Yr,

    Returns_3Yr,

    Returns_5Yr,

    Minus1,

    Minus2,

    Minus3,

    Minus4,

    YTD,

    PCT1Y0_MTH,

    PCT1Y1_MTH,

    PCT1Y2_MTH,

    PCT1Y3_MTH,

    PCT1Y4_MTH

    FROM @lipperid AS l

    INNER JOIN rpt.portfolioattributes AS pa

    ON pa.lipperid = l.lipperid

    INNER JOIN app.fund AS F

    ON f.id = pa.fundid

    INNER JOIN fs.FundIdentifier AS fi

    ON fi.LipperFundId= l.lipperid-- FS is varchar but implicit casting

    INNER JOIN lu.currency AS c

    ON c.id = F.CurrencyId-- force results only in base currency of fund. However there calENDar year records available in some other currencies

    LEFT JOIN (

    SELECT FundId,

    Currency, -- descriptive name

    -- start with cumuative performance measures

    SUM(CASE WHEN ProductId = 1 AND BasisId = 1 AND PeriodValue = 1 AND PeriodUnit = 'M' THEN PerformanceValue1 ELSE NULL END) AS Returns_1mth,

    SUM(CASE WHEN ProductId = 1 AND BasisId = 1 AND PeriodValue = 3 AND PeriodUnit = 'M' THEN PerformanceValue1 ELSE NULL END) AS Returns_3mth,

    SUM(CASE WHEN ProductId = 1 AND BasisId = 1 AND PeriodValue = 6 AND PeriodUnit = 'M' THEN PerformanceValue1 ELSE NULL END) AS Returns_6mth,

    SUM(CASE WHEN ProductId = 1 AND BasisId = 1 AND PeriodValue = 1 AND PeriodUnit = 'Y' THEN PerformanceValue1 ELSE NULL END) AS Returns_1Yr,

    SUM(CASE WHEN ProductId = 1 AND BasisId = 1 AND PeriodValue = 3 AND PeriodUnit = 'Y' THEN PerformanceValue1 ELSE NULL END) AS Returns_3Yr,

    SUM(CASE WHEN ProductId = 1 AND BasisId = 1 AND PeriodValue = 5 AND PeriodUnit = 'Y' THEN PerformanceValue1 ELSE NULL END) AS Returns_5Yr,

    -- now calENDar year

    SUM(CASE WHEN ProductId = 3 AND BasisId = 1 AND PeriodUnit = CAST(DATEPART(yy, @latestRptPerfReportDate)-1 AS varchar(4)) THEN PerformanceValue1 ELSE NULL END) AS Minus1,

    SUM(CASE WHEN ProductId = 3 AND BasisId = 1 AND PeriodUnit = CAST(DATEPART(yy, @latestRptPerfReportDate)-2 AS varchar(4)) THEN PerformanceValue1 ELSE NULL END) AS Minus2,

    SUM(CASE WHEN ProductId = 3 AND BasisId = 1 AND PeriodUnit = CAST(DATEPART(yy, @latestRptPerfReportDate)-3 AS varchar(4)) THEN PerformanceValue1 ELSE NULL END) AS Minus3,

    SUM(CASE WHEN ProductId = 3 AND BasisId = 1 AND PeriodUnit = CAST(DATEPART(yy, @latestRptPerfReportDate)-4 AS varchar(4)) THEN PerformanceValue1 ELSE NULL END) AS Minus4,

    -- Discrete Annual Returns

    SUM(CASE WHEN ProductId = 2 AND BasisId = 5 THEN PerformanceValue1 ELSE NULL END ) AS PCT1Y0_MTH,

    SUM(CASE WHEN ProductId = 2 AND BasisId = 6 THEN PerformanceValue1 ELSE NULL END ) AS PCT1Y1_MTH,

    SUM(CASE WHEN ProductId = 2 AND BasisId = 7 THEN PerformanceValue1 ELSE NULL END ) AS PCT1Y2_MTH,

    SUM(CASE WHEN ProductId = 2 AND BasisId = 8 THEN PerformanceValue1 ELSE NULL END ) AS PCT1Y3_MTH,

    SUM(CASE WHEN ProductId = 2 AND BasisId = 9 THEN PerformanceValue1 ELSE NULL END ) AS PCT1Y4_MTH,

    SUM(CASE WHEN ProductId = 3 AND BasisId = 1 AND PeriodValue = 1 THEN PerformanceValue1 ELSE NULL END) AS YTD -- wAS 4

    FROM fs.Performance AS p

    WHERE ReportDate = @latestrptPerfReportDate

    AND Note = 'Fund'

    GROUP BY FundId, Currency

    ) AS pf

    ON pf.fundid = fi.FundId

    AND pf.Currency = c.Name;

    SELECT TOP 1 @BaseCurrency = r.CurrencyCode, @BaseCurrencyName = c.Name

    FROM #results AS r

    INNER JOIN lu.Currency AS c

    ON r.CurrencyCode = c.Id

    -- IF there were no records found for funds THEN the BM AND Sector will also return no rows - BY design

    -- testing, show interim results

    --SELECT * FROM #results

    -- Add benchmarks, limited to the assumed commonly used CurrencyCode FROM funds

    INSERT INTO #results (

    RowType ,

    FundGroupId,

    FundId,

    fsFundId,

    Primary_Share_Class,

    LipperId,

    CurrencyCode,

    InceptionDate,

    ISIN,

    ShareClass,

    Returns_1mth,

    Returns_3mth,

    Returns_6mth,

    Returns_1Yr,

    Returns_3Yr,

    Returns_5Yr,

    Minus1,

    Minus2,

    Minus3,

    Minus4,

    YTD,

    PCT1Y0_MTH,

    PCT1Y1_MTH,

    PCT1Y2_MTH,

    PCT1Y3_MTH,

    PCT1Y4_MTH )

    SELECT DISTINCT

    'BM' AS RowType,

    NULL AS FundGroupId, -- will be SET with an UPDATE statement later, needed here AS SELECT into

    f.Id AS FundId, -- fundId (FT's app.Fund.Id )

    NULL ,

    pa.Primary_Share_Class,

    l.LipperId,

    c.Id AS CurrencyCode, -- different FROM Fund

    NULL, -- Benchmark's done have pa.InceptionDate

    f.AccountGr AS ISIN,

    NULL, -- Benchmarks don't have pa.ShareClass,

    Returns_1mth,

    Returns_3mth,

    Returns_6mth,

    Returns_1Yr,

    Returns_3Yr,

    Returns_5Yr,

    Minus1,

    Minus2,

    Minus3,

    Minus4,

    YTD,

    PCT1Y0_MTH,

    PCT1Y1_MTH,

    PCT1Y2_MTH,

    PCT1Y3_MTH,

    PCT1Y4_MTH

    FROM @lipperid AS l

    INNER JOIN app.fund F

    ON CAST(l.lipperid AS varchar(50))= CAST(f.AccountGr AS varchar(50))

    INNER JOIN rpt.portfolioattributes AS pa

    ON f.Id = pa.fundid

    INNER JOIN fs.FundIdentifier AS fi

    ON fi.LipperBenchmarkId= l.lipperid-- FS is varchar but implicit casting

    INNER JOIN @lipperid AS l_fund_level

    ON l_fund_level.LipperId = fi.LipperFundId /* Join back to @lipperId, this time again the fund, to restrict the SET of funds that contribute

    to those that will be shown in the tabular output, i.e. restrict distinc t versions available */

    INNER JOIN (

    SELECT FundId,

    Currency, -- descriptive name. BENCHMARKS SECTION

    -- start with cumuative performance measures

    SUM(CASE WHEN ProductId = 1 AND BasisId = 1 AND PeriodValue = 1 AND PeriodUnit = 'M' THEN PerformanceValue1 ELSE NULL END) AS Returns_1mth,

    SUM(CASE WHEN ProductId = 1 AND BasisId = 1 AND PeriodValue = 3 AND PeriodUnit = 'M' THEN PerformanceValue1 ELSE NULL END) AS Returns_3mth,

    SUM(CASE WHEN ProductId = 1 AND BasisId = 1 AND PeriodValue = 6 AND PeriodUnit = 'M' THEN PerformanceValue1 ELSE NULL END) AS Returns_6mth,

    SUM(CASE WHEN ProductId = 1 AND BasisId = 1 AND PeriodValue = 1 AND PeriodUnit = 'Y' THEN PerformanceValue1 ELSE NULL END) AS Returns_1Yr,

    SUM(CASE WHEN ProductId = 1 AND BasisId = 1 AND PeriodValue = 3 AND PeriodUnit = 'Y' THEN PerformanceValue1 ELSE NULL END) AS Returns_3Yr,

    SUM(CASE WHEN ProductId = 1 AND BasisId = 1 AND PeriodValue = 5 AND PeriodUnit = 'Y' THEN PerformanceValue1 ELSE NULL END) AS Returns_5Yr,

    -- now calENDar year

    SUM(CASE WHEN ProductId = 3 AND BasisId = 1 AND PeriodUnit = CAST(DATEPART(yy, @latestRptPerfReportDate)-1 AS varchar(4)) THEN PerformanceValue1 ELSE NULL END) AS Minus1,

    SUM(CASE WHEN ProductId = 3 AND BasisId = 1 AND PeriodUnit = CAST(DATEPART(yy, @latestRptPerfReportDate)-2 AS varchar(4)) THEN PerformanceValue1 ELSE NULL END) AS Minus2,

    SUM(CASE WHEN ProductId = 3 AND BasisId = 1 AND PeriodUnit = CAST(DATEPART(yy, @latestRptPerfReportDate)-3 AS varchar(4)) THEN PerformanceValue1 ELSE NULL END) AS Minus3,

    SUM(CASE WHEN ProductId = 3 AND BasisId = 1 AND PeriodUnit = CAST(DATEPART(yy, @latestRptPerfReportDate)-4 AS varchar(4)) THEN PerformanceValue1 ELSE NULL END) AS Minus4,

    -- Discrete Annual Returns

    SUM(CASE WHEN ProductId = 2 AND BasisId = 5 THEN PerformanceValue1 ELSE NULL END) AS PCT1Y0_MTH,

    SUM(CASE WHEN ProductId = 2 AND BasisId = 6 THEN PerformanceValue1 ELSE NULL END) AS PCT1Y1_MTH,

    SUM(CASE WHEN ProductId = 2 AND BasisId = 7 THEN PerformanceValue1 ELSE NULL END) AS PCT1Y2_MTH,

    SUM(CASE WHEN ProductId = 2 AND BasisId = 8 THEN PerformanceValue1 ELSE NULL END) AS PCT1Y3_MTH,

    SUM(CASE WHEN ProductId = 2 AND BasisId = 9 THEN PerformanceValue1 ELSE NULL END) AS PCT1Y4_MTH,

    SUM(CASE WHEN ProductId = 3 AND BasisId = 1 AND PeriodValue = 1 THEN PerformanceValue1 ELSE NULL END) AS YTD

    FROM fs.Performance AS p

    WHERE ReportDate = @latestrptPerfReportDate

    AND Note = 'Benchmark'

    AND Currency = @BaseCurrencyName

    GROUP BY FundId, Currency

    ) AS pf

    ON pf.fundid = fi.FundId

    INNER JOIN lu.currency AS c

    ON c.Name = pF.Currency

    -- AND pf.Currency = c.Name

    /* actually slows overall time

    CREATE index ix_results_fundgroupid ON #results(FundGroupId)

    */

    -- Add SECTORS

    INSERT INTO #results (

    RowType ,

    FundGroupId, -- will be SET with an UPDATE statement later, needed here AS SELECT into

    FundId, -- fundId (FT's app.Fund.Id )

    fsFundId,

    Primary_Share_Class,

    LipperId,

    CurrencyCode,

    InceptionDate,

    ISIN,

    ShareClass,

    Returns_1mth,

    Returns_3mth,

    Returns_6mth,

    Returns_1Yr,

    Returns_3Yr,

    Returns_5Yr,

    Minus1,

    Minus2,

    Minus3,

    Minus4,

    YTD,

    PCT1Y0_MTH,

    PCT1Y1_MTH,

    PCT1Y2_MTH,

    PCT1Y3_MTH,

    PCT1Y4_MTH )

    SELECT DISTINCT -- DISTINCT AS many of the FS funds will share the sector AND only one is needeed. They are not necessarily consistent (thank you Aberdeen for multiple differentcopies of what should be a single copy of this data)

    'S' AS RowType,

    NULL AS FundGroupId, -- will be SET with an UPDATE statement later, needed here AS SELECT into

    f.Id AS FundId, -- fundId (FT's app.Fund.Id )

    pf.FundId AS fsFundId,

    pa.Primary_Share_Class,

    l.LipperId,

    c.Id AS CurrencyCode, -- different FROM Fund

    NULL, -- Sectors done have pa.InceptionDate

    f.AccountGr AS ISIN,

    NULL, -- Sectors don't have pa.ShareClass,

    Returns_1mth,

    Returns_3mth,

    Returns_6mth,

    Returns_1Yr,

    Returns_3Yr,

    Returns_5Yr,

    Minus1,

    Minus2,

    Minus3,

    Minus4,

    YTD,

    PCT1Y0_MTH,

    PCT1Y1_MTH,

    PCT1Y2_MTH,

    PCT1Y3_MTH,

    PCT1Y4_MTH

    FROM @lipperid AS l

    INNER JOIN app.fund AS F

    ON CAST(l.lipperid AS varchar(50)) = CAST(f.AccountGr AS varchar(50))

    LEFT JOIN rpt.portfolioattributes AS pa -- changed 04 March 2013

    ON f.Id = pa.fundid

    LEFT JOIN fs.FundIdentifier AS fi

    ON fi.LipperSectorId = l.lipperid -- FS is varchar but implicit casting

    LEFT JOIN @lipperid AS l_fund_level

    ON l_fund_level.LipperId = fi.LipperFundId /* Join back to @lipperId, this time again the fund, to restrict the SET of funds that contribute

    to those that will be shown in the tabular output, i.e. restrict distinc t versions available */

    INNER JOIN (

    SELECT FundId,

    Currency, -- descriptive name. BENCHMARKS SECTION

    -- start with cumuative performance measures

    SUM(CASE WHEN ProductId = 1 AND BasisId = 1 AND PeriodValue = 1 AND PeriodUnit = 'M' THEN PerformanceValue1 ELSE NULL END ) AS Returns_1mth,

    SUM(CASE WHEN ProductId = 1 AND BasisId = 1 AND PeriodValue = 3 AND PeriodUnit = 'M' THEN PerformanceValue1 ELSE NULL END ) AS Returns_3mth,

    SUM(CASE WHEN ProductId = 1 AND BasisId = 1 AND PeriodValue = 6 AND PeriodUnit = 'M' THEN PerformanceValue1 ELSE NULL END ) AS Returns_6mth,

    SUM(CASE WHEN ProductId = 1 AND BasisId = 1 AND PeriodValue = 1 AND PeriodUnit = 'Y' THEN PerformanceValue1 ELSE NULL END ) AS Returns_1Yr,

    SUM(CASE WHEN ProductId = 1 AND BasisId = 1 AND PeriodValue = 3 AND PeriodUnit = 'Y' THEN PerformanceValue1 ELSE NULL END ) AS Returns_3Yr,

    SUM(CASE WHEN ProductId = 1 AND BasisId = 1 AND PeriodValue = 5 AND PeriodUnit = 'Y' THEN PerformanceValue1 ELSE NULL END ) AS Returns_5Yr,

    -- now calENDar year

    SUM(CASE WHEN ProductId = 3 AND BasisId = 1 AND PeriodUnit = CAST(DATEPART(yy, @latestRptPerfReportDate)-1 AS varchar(4)) THEN PerformanceValue1 ELSE NULL END ) AS Minus1,

    SUM(CASE WHEN ProductId = 3 AND BasisId = 1 AND PeriodUnit = CAST(DATEPART(yy, @latestRptPerfReportDate)-2 AS varchar(4)) THEN PerformanceValue1 ELSE NULL END ) AS Minus2,

    SUM(CASE WHEN ProductId = 3 AND BasisId = 1 AND PeriodUnit = CAST(DATEPART(yy, @latestRptPerfReportDate)-3 AS varchar(4)) THEN PerformanceValue1 ELSE NULL END ) AS Minus3,

    SUM(CASE WHEN ProductId = 3 AND BasisId = 1 AND PeriodUnit = CAST(DATEPART(yy, @latestRptPerfReportDate)-4 AS varchar(4)) THEN PerformanceValue1 ELSE NULL END ) AS Minus4,

    -- Discrete Annual Returns. Not expected to exist

    SUM(CASE WHEN ProductId = 2 AND BasisId = 5 THEN PerformanceValue1 ELSE NULL END ) AS PCT1Y0_MTH,

    SUM(CASE WHEN ProductId = 2 AND BasisId = 6 THEN PerformanceValue1 ELSE NULL END ) AS PCT1Y1_MTH,

    SUM(CASE WHEN ProductId = 2 AND BasisId = 7 THEN PerformanceValue1 ELSE NULL END ) AS PCT1Y2_MTH,

    SUM(CASE WHEN ProductId = 2 AND BasisId = 8 THEN PerformanceValue1 ELSE NULL END ) AS PCT1Y3_MTH,

    SUM(CASE WHEN ProductId = 2 AND BasisId = 9 THEN PerformanceValue1 ELSE NULL END ) AS PCT1Y4_MTH,

    SUM(CASE WHEN ProductId = 3 AND BasisId = 1 AND PeriodValue = 1 THEN PerformanceValue1 ELSE NULL END ) AS YTD

    FROM fs.Performance AS p

    WHERE ReportDate = @latestrptPerfReportDate

    AND Note = 'Sector'

    AND Currency = @BaseCurrencyName

    /* force this fund to be in the list of LipperIds of interest so we don't get all possible variations of this sectors,

    but onoly variations WHERE we are going to report data ON the fund level AS well. This will strip out needless variation

    */

    GROUP BY FundId, Currency-- this logic is a little overblown now, AS we are using a single currency, so could be simplified

    ) AS pf

    ON pf.fundid = fi.FundId

    INNER JOIN lu.currency AS c

    ON c.name = pF.Currency

    -- Hide values IF incepted <1 yr ago. If no inceptiON date assume is old.

    -- If website parameter is NULL this will not work, but that parameter will always be SET in normal operatiON of website.

    UPDATE r

    SET returns_1mth = NULL,

    returns_3mth = NULL,

    returns_6mth = NULL,

    Returns_1Yr = NULL,

    Returns_3yr = NULL,

    Returns_5yr = NULL,

    YTD = NULL,

    Minus1=NULL,

    Minus2 = NULL,

    Minus3 = NULL,

    Minus4 = NULL,

    PCT1Y0_MTH = NULL,

    PCT1Y1_MTH = NULL,

    PCT1Y2_MTH = NULL,

    PCT1Y3_MTH = NULL,

    PCT1Y4_MTH = NULL

    FROM #results AS r

    WHERE InceptionDate > DATEADD(mm, -13, GETDATE())

    AND RowType = 'Fund'

    /* AND @WebsiteId = @RetailWebsite */

    -- Insert any that are missing - need to do here rather than LEFT JOIN later AS setting names is messy

    /* INSERT INTO #Results(LipperId)

    SELECT LipperId FROM @LipperId l

    WHERE not EXISTS ( SELECT * FROM #results r WHERE r.LipperId = l.LipperId)

    */

    -- Set the FundName AND FundShortName.

    -- for 'funds'

    UPDATE r

    SET FundId = f.id, FundName = f.fullname , FundShortName = f.ShortName

    FROM #results AS r

    INNER JOIN rpt.PortfolioAttributes AS pa

    ON pa.LipperId = r.LipperId

    INNER JOIN app.Fund AS f

    ON pa.Fundid = f.Id;

    -- for 'benchmarks' AND 'sectors' (represented differently)

    UPDATE r

    SET FundId = f.id, FundName = f.fullname , FundShortName = f.ShortName

    FROM #results AS r

    INNER JOIN app.Fund AS f

    ON CAST(r.LipperId AS varchar(100)) = AccountGr;

    -- SET the FundGroupId (take FROM any range even IF in multiple, so a random matching row will do)

    UPDATE r

    SET FundGroupId = fd.FundGroupId

    FROM #results r

    INNER JOIN rpt.funddisplay AS fd

    ON r.FundId = fd.FundId;

    -- Fund renaming (IF changing find other copies of this code */

    DECLARE @FundGroupId int, @FromText varchar(255), @ToText varchar(255), @fetch_status1 int;

    -- we don't have a simple parameter to work with so we have to JOINn FROM #results to find range (or possibly multiple ranges).

    /*

    DECLARE Replacements AS CURSOR

    FOR SELECT DISTINCT fr.FundGroupId, FromText, ToText

    FROM rpt.FundRename AS fr

    INNER JOIN #results AS r

    ON fr.fundgroupid = r.fundgroupid

    OPEN Replacements

    FETCH NEXT FROM Replacements INTO @FundGroupId, @FromText, @ToText

    SET @fetch_status1 = @@fetch_status

    WHILE @fetch_status1 <> -1 -- in CASE we need the @@fetch_status later AND it is confused BY the inner cursor

    BEGIN

    UPDATE #Results

    SET FundName = REPLACE(fundname, @fromtext, COALESCE(@totext,''))

    WHERE FundGroupId = @fundGroupId -- in CASE we have a mix of fundgroups

    FETCH NEXT FROM Replacements INTO @FundGroupId, @FromText, @ToText

    SET @fetch_status1 = @@fetch_status

    END

    */

    -- THIS IS THE CURSOR REPLACEMENT

    WITH DISTINCT_FUND_RENAMES AS (

    SELECT DISTINCT fr.FundGroupId, FromText, ToText

    FROM rpt.FundRename AS fr

    INNER JOIN #results AS r

    ON fr.fundgroupid = r.fundgroupid

    )

    UPDATE R

    SET R.FundName = REPLACE(R.FundName, DFR.FromText, COALESCE(DFR.ToText,''))

    FROM #results AS R

    INNER JOIN DISTINCT_FUND_RENAMES AS DFR

    ON R.FundGroupId = DFR.FundGroupId

    -- remove any secondary sectors WHERE a primary exists

    DELETE r

    FROM #results AS r

    INNER JOIN fs.fundidentifier AS fi

    ON fi.fundid = r.fsfundid

    INNER JOIN rpt.vwFund AS f

    ON f.AccountGr = fi.ISIN

    INNER JOIN rpt.PortfolioAttributes AS pa

    ON pa.Fundid = f.Id

    WHERE pa.Primary_Share_Class = 'No'

    AND EXISTS (

    SELECT *

    FROM #results AS r1

    INNER JOIN fs.fundidentifier AS fi1

    ON fi1.fundid = r1.fsfundid

    INNER JOIN rpt.vwFund AS f1

    ON f1.AccountGr = fi1.ISIN

    INNER JOIN rpt.portfolioattributes AS pa1

    ON pa1.fundid = f1.id

    WHERE pa1.Primary_Share_Class = 'Yes'

    AND r1.lipperid = r.lipperid)

    DELETE FROM r2

    FROM #results AS r1

    INNER JOIN #results AS r2

    ON r2.FundId = r1.FundId

    WHERE r2.fsFundId > r1.fsFundId

    -- return results. Have to substitute NULL s for the calENDar year ones AS not done ON websie

    SELECT DISTINCT

    l.idx AS RowNumber,

    r.RowType,

    r.Primary_Share_Class,

    @latestrptPerfReportDate AS ReportDate,

    r.FundGroupId,

    fg.Name,

    r.FundId,

    -- r.fsFundId,

    l.LipperId,

    r.CurrencyCode,

    r.InceptionDate,

    r.ISIN,

    r.FundName,

    r.FundShortName,

    r.ShareClass,

    COALESCE(CAST(CAST(r.Returns_1mth AS decimal(10,2)) AS varchar(20)),'-') AS Returns_1mth,

    COALESCE(CAST(CAST(r.Returns_3mth AS decimal(10,2)) AS varchar(20)),'-') AS Returns_3mth,

    COALESCE(CAST(CAST(r.Returns_6mth AS decimal(10,2)) AS varchar(20)),'-') AS Returns_6mth,

    COALESCE(CAST(CAST(r.Returns_1Yr AS decimal(10,2)) AS varchar(20)),'-') AS Returns_1Yr,

    COALESCE(CAST(CAST(r.Returns_3Yr AS decimal(10,2)) AS varchar(20)),'-') AS Returns_3Yr,

    COALESCE(CAST(CAST(r.Returns_5Yr AS decimal(10,2)) AS varchar(20)),'-') AS Returns_5Yr,

    COALESCE(CAST(CAST(r.YTD AS decimal(10,2)) AS varchar(20)),'-') AS YTD,

    --, 0 AS Minus1

    COALESCE(CAST(CAST(r.Minus1 AS decimal(10,2)) AS varchar(20)),'-') AS Minus1,

    COALESCE(CAST(CAST(r.Minus2 AS decimal(10,2)) AS varchar(20)),'-') AS Minus2,

    COALESCE(CAST(CAST(r.Minus3 AS decimal(10,2)) AS varchar(20)),'-') AS Minus3,

    COALESCE(CAST(CAST(r.Minus4 AS decimal(10,2)) AS varchar(20)),'-') AS Minus4,

    COALESCE(CAST(CAST(r.PCT1Y0_MTH AS decimal(10,2)) AS varchar(20)),'-') AS PCT1Y0_MTH,

    COALESCE(CAST(CAST(r.PCT1Y1_MTH AS decimal(10,2)) AS varchar(20)),'-') AS PCT1Y1_MTH,

    COALESCE(CAST(CAST(r.PCT1Y2_MTH AS decimal(10,2)) AS varchar(20)),'-') AS PCT1Y2_MTH,

    COALESCE(CAST(CAST(r.PCT1Y3_MTH AS decimal(10,2)) AS varchar(20)),'-') AS PCT1Y3_MTH,

    COALESCE(CAST(CAST(r.PCT1Y4_MTH AS decimal(10,2)) AS varchar(20)),'-') AS PCT1Y4_MTH

    FROM @lipperid AS l

    INNER JOIN #results AS r-- inner JOIN AS we are guaranteed to ahve them (due to inserting any missing earlier)

    ON l.LipperId = r.LipperId

    LEFT JOIN rpt.fundGROUP AS fg

    ON r.fundgroupid = fg.id

    ORDER BY l.Idx -- IF using triplet THEN will depEND ON earlier INSERT INTO @lipperId being ordered

    Steve (aka sgmunson) πŸ™‚ πŸ™‚ πŸ™‚
    Rent Servers for Income (picks and shovels strategy)

  • Thanks for the replies.

    Well, I am trying to fix something that was already in place for sometime and I have no clue on how the front end works. So the only thing i can do is restructuring of this proc but can't remove or add anything that makes end results look different. This proc is executed quite often in a day. If I can get some clues on the topmost things that need immediate attention, I can bang my head against those.

    Thanks again for your views on this.:-)

  • Yogeshwar Phull (8/6/2015)


    Thanks for the replies.

    Well, I am trying to fix something that was already in place for sometime and I have no clue on how the front end works. So the only thing i can do is restructuring of this proc but can't remove or add anything that makes end results look different. This proc is executed quite often in a day. If I can get some clues on the topmost things that need immediate attention, I can bang my head against those.

    Thanks again for your views on this.:-)

    Probably the lowest hanging fruit here is the tempdb configurations, earlier filtering of the sets in Q5 and Q6 and possibly changed the way the @lipperid table variable is used.

    😎

  • 1) I see vw..., indicating likely views. Lord only knows what those have in them

    2) Table variables should be replaced with temp tables

    3) Someone got rid of the silly cursor

    4) you have functions around columns in WHERE clause. Number one thing that needs to go there

    Best,
    Kevin G. Boles
    SQL Server Consultant
    SQL MVP 2007-2012
    TheSQLGuru on googles mail service

  • Those three aggregate subqueries queries against the 'performance' table appear to be very expensive. Why not do them in one shot, which would offer you tuning possibilities for that component in isolation from the rest of the query. Try this hack. When the results are the same (if they are not, you're a developer, figure it out) then post up the Actual execution plan grabbed from an execution with a decent amount of data running through it, and provide us with some idea of performance against the original version.

    Edit: check that your statistics are up to date. Actual and expected rows for the 'performance' table are wildly different.

    USE [KSDK_157]

    GO

    /****** Object: StoredProcedure [rpi].[udf_GetPerformance_FS] Script Date: 05/08/2015 12:07:44 ******/

    SET ANSI_NULLS ON

    GO

    SET QUOTED_IDENTIFIER ON

    GO

    CREATE procedure [rpi].[udf_GetPerformance_FS] (

    @LipperIdList VARCHAR(MAX) = ''

    ,@FundsOnly char(1) = '0', -- really a bit but FundNets can't do that. Default 0 in case not passed

    @LanguageId varchar(10) = '',

    @WebsiteId varchar(10) = '',

    @RangeId varchar(10) = ''

    )

    AS

    BEGIN

    select @languageid = case when isnumeric(@languageid)=1 then @languageid else null end

    select @websiteid = case when isnumeric(@websiteid)=1 then @websiteid else null end

    select @rangeid = case when isnumeric(@rangeid)=1 then @rangeid else null end

    declare @LipperId table (LipperId int, Idx INT IDENTITY(1,1)) --FundGroupId will be NULL if providing a LipperId list, only populated if using triplet

    declare @BaseCurrency char(3), @BaseCurrencyName varchar(100)

    -- we will always return the latest data, but the FS tables will accumulate multiple ReportDates

    declare @latestrptPerfReportDate date

    select @latestrptPerfReportDate = max(reportdate) from fs.performance where ReportDate < DATEADD(day, -14,getdate())

    /*

    declare @RetailWebsite int

    select @RetailWebsite = id from rpt.website where name = 'Retail'

    */

    -- start by inserting based on @LipperIdList

    insert into @LipperId (LipperId)

    select String

    from app.udf_SplitString(@LipperIdList, ',')

    ORDER BY idx -- we will respect the order they were sent in

    if @WebsiteId is not null -- sufficient to test just one of the three parameters to see if we are using this approach

    begin

    insert into @LipperId (LipperId)

    select fl.LipperId

    from

    [rpi].[udf_FC_GetFundList] (@LanguageId, @WebsiteId, @RangeId, null /* spotlight */, null /* sandprating */) fl

    order by RangeName, fundname, shareclass

    /*

    from [rpi].[udf_GetFundList_Brief] (@LanguageId, @WebsiteId, @RangeId) fl

    join rpt.portfolioattributes pa

    on fl.fundid = pa.fundid

    */

    end

    --/* Not everyone in our list may have the same date values available, so we take the worst, to ensure all funds have something to display

    ----*/

    if @FundsOnly = 1

    -- we throw out anything that isn't a fund-type lipperid. We do this indirectly as we don't have a fundtype/securitytype to rely upon and these are also in app.fund

    begin

    delete from @LipperId

    where Lipperid in ( -- automatically distinct

    select LipperBenchmarkId from rpt.portfolioattributes

    union select lippersectorid from rpt.portfolioattributes )

    end

    create table #results( idx int,

    RowType varchar(10), -- 'Fund' / 'BM', 'S'

    FundGroupId int, -- will be set with an update statement later

    FundId int, -- fundId (FT's app.Fund.Id )

    fsFundId int,

    Primary_Share_Class varchar(3),

    LipperId int,

    CurrencyCode char(3),

    InceptionDate date,

    ISIN varchar(12),

    FundName varchar(100),

    FundShortName varchar(100),

    ShareClass varchar(100),

    Returns_1mth decimal(20,10),

    Returns_3mth decimal(20,10),

    Returns_6mth decimal(20,10),

    Returns_1Yr decimal(20,10),

    Returns_3Yr decimal(20,10),

    Returns_5Yr decimal(20,10),

    Minus1 decimal(20,10),

    Minus2 decimal(20,10),

    Minus3 decimal(20,10),

    Minus4 decimal(20,10),

    YTD decimal(20,10),

    PCT1Y0_MTH decimal(20,10),

    PCT1Y1_MTH decimal(20,10),

    PCT1Y2_MTH decimal(20,10),

    PCT1Y3_MTH decimal(20,10),

    PCT1Y4_MTH decimal(20,10)

    )

    -------------------------------------------------------------------------------------------------------------------------------

    SELECT

    FundId,

    Currency, -- descriptive name

    Note,

    -- start with cumuative performance measures

    sum(case when ProductId = 1 and BasisId = 1 and PeriodValue = 1 and PeriodUnit = 'M' then PerformanceValue1 else null end ) as Returns_1mth,

    sum(case when ProductId = 1 and BasisId = 1 and PeriodValue = 3 and PeriodUnit = 'M' then PerformanceValue1 else null end ) as Returns_3mth,

    sum(case when ProductId = 1 and BasisId = 1 and PeriodValue = 6 and PeriodUnit = 'M' then PerformanceValue1 else null end ) as Returns_6mth,

    sum(case when ProductId = 1 and BasisId = 1 and PeriodValue = 1 and PeriodUnit = 'Y' then PerformanceValue1 else null end ) as Returns_1Yr,

    sum(case when ProductId = 1 and BasisId = 1 and PeriodValue = 3 and PeriodUnit = 'Y' then PerformanceValue1 else null end ) as Returns_3Yr,

    sum(case when ProductId = 1 and BasisId = 1 and PeriodValue = 5 and PeriodUnit = 'Y' then PerformanceValue1 else null end ) as Returns_5Yr,

    -- now calendar year

    sum(case when ProductId = 3 and BasisId = 1 and PeriodUnit = cast(datepart(yy, @latestRptPerfReportDate)-1 as varchar(4)) then PerformanceValue1 else null end ) as Minus1,

    sum(case when ProductId = 3 and BasisId = 1 and PeriodUnit = cast(datepart(yy, @latestRptPerfReportDate)-2 as varchar(4)) then PerformanceValue1 else null end ) as Minus2,

    sum(case when ProductId = 3 and BasisId = 1 and PeriodUnit = cast(datepart(yy, @latestRptPerfReportDate)-3 as varchar(4)) then PerformanceValue1 else null end ) as Minus3,

    sum(case when ProductId = 3 and BasisId = 1 and PeriodUnit = cast(datepart(yy, @latestRptPerfReportDate)-4 as varchar(4)) then PerformanceValue1 else null end ) as Minus4,

    -- Discrete Annual Returns

    sum(case when ProductId = 2 and BasisId = 5 then PerformanceValue1 else null end ) as PCT1Y0_MTH,

    sum(case when ProductId = 2 and BasisId = 6 then PerformanceValue1 else null end ) as PCT1Y1_MTH,

    sum(case when ProductId = 2 and BasisId = 7 then PerformanceValue1 else null end ) as PCT1Y2_MTH,

    sum(case when ProductId = 2 and BasisId = 8 then PerformanceValue1 else null end ) as PCT1Y3_MTH,

    sum(case when ProductId = 2 and BasisId = 9 then PerformanceValue1 else null end ) as PCT1Y4_MTH,

    sum(case when ProductId = 3 and BasisId = 1 and PeriodValue = 1 then PerformanceValue1 else null end ) as YTD -- was 4

    INTO #Performance

    FROM fs.Performance p

    WHERE ReportDate = @latestrptPerfReportDate

    AND Note IN ('Fund','Benchmark','Sector')

    AND ProductId IN (1,2,3)

    --and ProductId = 1-- Discrete performance

    -- and BasisId = 1

    GROUP BY FundId, Currency

    CREATE CLUSTERED INDEX cx_Helper ON #Performance (FundId,Currency,Note)

    -------------------------------------------------------------------------------------------------------------------------------

    -- main select

    insert into #results (

    RowType ,

    FundGroupId ,

    FundId,

    Primary_Share_Class,

    LipperId ,

    CurrencyCode ,

    InceptionDate ,

    ISIN,

    ShareClass,

    Returns_1mth ,

    Returns_3mth ,

    Returns_6mth ,

    Returns_1Yr ,

    Returns_3Yr,

    Returns_5Yr ,

    Minus1 ,

    Minus2 ,

    Minus3 ,

    Minus4 ,

    YTD ,

    PCT1Y0_MTH ,

    PCT1Y1_MTH ,

    PCT1Y2_MTH,

    PCT1Y3_MTH ,

    PCT1Y4_MTH )

    select

    'Fund' as RowType, -- 'Fund' in the case of these records.

    Null as FundGroupId, -- will be set with an update statement later, needed here as select into

    f.Id as FundId, -- fundId (FT's app.Fund.Id )

    pa.Primary_Share_Class,

    l.LipperId,

    f.CurrencyId as CurrencyCode,

    pa.InceptionDate,

    f.AccountGr as ISIN,

    pa.ShareClass,

    Returns_1mth,

    Returns_3mth,

    Returns_6mth,

    Returns_1Yr,

    Returns_3Yr,

    Returns_5Yr,

    Minus1,

    Minus2,

    Minus3,

    Minus4,

    YTD,

    PCT1Y0_MTH,

    PCT1Y1_MTH,

    PCT1Y2_MTH,

    PCT1Y3_MTH,

    PCT1Y4_MTH

    from @lipperid l

    join rpt.portfolioattributes pa

    on pa.lipperid = l.lipperid

    join app.fund F

    on f.id = pa.fundid

    join fs.FundIdentifier fi

    on fi.LipperFundId= l.lipperid -- FS is varchar but implicit casting

    join lu.currency c

    on c.id = F.CurrencyId -- force results only in base currency of fund. However there calendar year records available in some other currencies

    LEFT JOIN #Performance pf

    ON pf.fundid = fi.FundId

    AND pf.Currency = c.Name

    AND pf.Note = 'Fund'

    -------------------------------------------------------------------------------------------------------------------------------------------

    select top 1 @BaseCurrency = CurrencyCode , @BaseCurrencyName = c.Name

    from #results r

    join lu.Currency c

    on r.CurrencyCode = c.Id

    -- if there were no records found for funds then the BM and Sector will also return no rows - by design

    -- testing, show interim results

    --select * from #results

    -- Add benchmarks, limited to the assumed commonly used CurrencyCode from funds

    insert into #results (

    RowType ,

    FundGroupId,

    FundId,

    fsFundId,

    Primary_Share_Class,

    LipperId,

    CurrencyCode,

    InceptionDate,

    ISIN,

    ShareClass,

    Returns_1mth,

    Returns_3mth,

    Returns_6mth,

    Returns_1Yr,

    Returns_3Yr,

    Returns_5Yr,

    Minus1,

    Minus2,

    Minus3,

    Minus4,

    YTD,

    PCT1Y0_MTH,

    PCT1Y1_MTH,

    PCT1Y2_MTH,

    PCT1Y3_MTH,

    PCT1Y4_MTH )

    select

    distinct

    'BM' as RowType,

    Null as FundGroupId, -- will be set with an update statement later, needed here as select into

    f.Id as FundId, -- fundId (FT's app.Fund.Id )

    null,

    pa.Primary_Share_Class,

    l.LipperId,

    c.Id as CurrencyCode, -- different from Fund

    NULL, -- Benchmark's done have pa.InceptionDate

    f.AccountGr as ISIN,

    NULL, -- Benchmarks don't have pa.ShareClass,

    Returns_1mth,

    Returns_3mth,

    Returns_6mth,

    Returns_1Yr,

    Returns_3Yr,

    Returns_5Yr,

    Minus1,

    Minus2,

    Minus3,

    Minus4,

    YTD,

    PCT1Y0_MTH,

    PCT1Y1_MTH,

    PCT1Y2_MTH,

    PCT1Y3_MTH,

    PCT1Y4_MTH

    from @lipperid l

    join app.fund F

    on cast(l.lipperid as varchar(50))= cast(f.AccountGr as varchar(50))

    join rpt.portfolioattributes pa

    on f.Id = pa.fundid

    join fs.FundIdentifier fi

    on fi.LipperBenchmarkId= l.lipperid -- FS is varchar but implicit casting

    join @lipperid l_fund_level

    on l_fund_level.LipperId = fi.LipperFundId /* Join back to @lipperId, this time again the fund, to restrict the set of funds that contribute

    to those that will be shown in the tabular output, i.e. restrict distinc t versions available */

    INNER JOIN #Performance pf

    ON pf.fundid = fi.FundId

    AND pf.Note = 'Benchmark'

    AND pf.Currency = @BaseCurrencyName

    INNER JOIN lu.currency c

    on c.Name = pF.Currency

    -- and pf.Currency = c.Name

    /* actually slows overall time

    create index ix_results_fundgroupid on #results(FundGroupId)

    */

    -- Add SECTORS

    insert into #results (

    RowType ,

    FundGroupId, -- will be set with an update statement later, needed here as select into

    FundId, -- fundId (FT's app.Fund.Id )

    fsFundId,

    Primary_Share_Class,

    LipperId,

    CurrencyCode,

    InceptionDate,

    ISIN,

    ShareClass,

    Returns_1mth,

    Returns_3mth,

    Returns_6mth,

    Returns_1Yr,

    Returns_3Yr,

    Returns_5Yr,

    Minus1,

    Minus2,

    Minus3,

    Minus4,

    YTD,

    PCT1Y0_MTH,

    PCT1Y1_MTH,

    PCT1Y2_MTH,

    PCT1Y3_MTH,

    PCT1Y4_MTH )

    select

    distinct -- distinct as many of the FS funds will share the sector and only one is needeed. They are not necessarily consistent (thank you Aberdeen for multiple differentcopies of what should be a single copy of this data)

    'S' as RowType,

    Null as FundGroupId, -- will be set with an update statement later, needed here as select into

    f.Id as FundId, -- fundId (FT's app.Fund.Id )

    pf.FundId as fsFundId,

    pa.Primary_Share_Class,

    l.LipperId,

    c.Id as CurrencyCode, -- different from Fund

    NULL, -- Sectors done have pa.InceptionDate

    f.AccountGr as ISIN,

    NULL, -- Sectors don't have pa.ShareClass,

    Returns_1mth,

    Returns_3mth,

    Returns_6mth,

    Returns_1Yr,

    Returns_3Yr,

    Returns_5Yr,

    Minus1,

    Minus2,

    Minus3,

    Minus4,

    YTD,

    PCT1Y0_MTH,

    PCT1Y1_MTH,

    PCT1Y2_MTH,

    PCT1Y3_MTH,

    PCT1Y4_MTH

    from @lipperid l

    join app.fund F

    on cast(l.lipperid as varchar(50))= cast(f.AccountGr as varchar(50))

    left join rpt.portfolioattributes pa -- changed 04 March 2013

    on f.Id = pa.fundid

    left join fs.FundIdentifier fi

    on fi.LipperSectorId= l.lipperid -- FS is varchar but implicit casting

    left join @lipperid l_fund_level

    on l_fund_level.LipperId = fi.LipperFundId /* Join back to @lipperId, this time again the fund, to restrict the set of funds that contribute

    to those that will be shown in the tabular output, i.e. restrict distinc t versions available */

    INNER JOIN #Performance pf

    ON pf.fundid = fi.FundId

    AND pf.Note = 'Sector'

    AND pf.Currency = @BaseCurrencyName

    join lu.currency c

    on c.name = pF.Currency

    -- Hide values if incepted <1 yr ago. If no inception date assume is old.

    -- If website parameter is null this will not work, but that parameter will always be set in normal operation of website.

    update r

    set returns_1mth = null,

    returns_3mth = null,

    returns_6mth = null,

    Returns_1Yr = null,

    Returns_3yr = null,

    Returns_5yr = null,

    YTD = null,

    Minus1=null,

    Minus2 = null,

    Minus3 = null,

    Minus4 = null,

    PCT1Y0_MTH = null,

    PCT1Y1_MTH = null,

    PCT1Y2_MTH = null,

    PCT1Y3_MTH = null,

    PCT1Y4_MTH = null

    from #results r

    where InceptionDate>dateadd(mm, -13, getdate())

    and RowType = 'Fund'

    /* and @WebsiteId = @RetailWebsite */

    -- Insert any that are missing - need to do here rather than left join later as setting names is messy

    /* insert into #Results(LipperId)

    select LipperId from @LipperId l

    where not exists ( select * from #results r where r.LipperId = l.LipperId)

    */

    -- Set the FundName and FundShortName.

    -- for 'funds'

    update r set FundId = f.id, FundName = f.fullname , FundShortName = f.ShortName

    from #results r

    join rpt.PortfolioAttributes pa

    on pa.LipperId = r.LipperId

    join app.Fund f

    on pa.Fundid = f.Id

    -- for 'benchmarks' and 'sectors' (represented differently)

    update r set FundId = f.id, FundName = f.fullname , FundShortName = f.ShortName

    from #results r

    join app.Fund f

    on cast(r.LipperId as varchar(100)) = AccountGr

    -- set the FundGroupId (take from any range even if in multiple, so a random matching row will do)

    update r

    set FundGroupId = fd.FundGroupId

    from #results r

    join rpt.funddisplay fd

    on r.FundId = fd.FundId

    -- Fund renaming (if changing find other copies of this code */

    declare @FundGroupId int, @FromText varchar(255), @ToText varchar(255), @fetch_status1 int

    -- we don't have a simple parameter to work with so we have to joinn from #results to find range (or possibly multiple ranges).

    declare Replacements cursor

    for select distinct fr.FundGroupId, FromText, ToText from

    rpt.FundRename fr

    join #results r

    on fr.fundgroupid = r.fundgroupid

    open Replacements

    fetch next from Replacements into @FundGroupId, @FromText, @ToText

    set @fetch_status1 = @@fetch_status

    while @fetch_status1 <> -1 -- in case we need the @@fetch_status later and it is confused by the inner cursor

    begin

    update #Results

    set FundName = replace(fundname, @fromtext, coalesce(@totext,''))

    where FundGroupId = @fundGroupId -- in case we have a mix of fundgroups

    fetch next from Replacements into @FundGroupId, @FromText, @ToText

    set @fetch_status1 = @@fetch_status

    end

    -- remove any secondary sectors where a primary exists

    delete from r

    from #results r

    join

    fs.fundidentifier fi on fi.fundid = r.fsfundid

    join

    rpt.vwFund f on f.AccountGr = fi.ISIN

    join

    rpt.PortfolioAttributes pa on pa.Fundid = f.Id

    where pa.Primary_Share_Class = 'No'

    and exists (select *

    from #results r1

    join

    fs.fundidentifier fi1 on fi1.fundid = r1.fsfundid

    join

    rpt.vwFund f1 on f1.AccountGr = fi1.ISIN

    join

    rpt.portfolioattributes pa1 on pa1.fundid = f1.id

    where pa1.Primary_Share_Class = 'Yes'

    and r1.lipperid = r.lipperid)

    delete from r2

    from #results r1

    inner join

    #results r2 on r2.FundId = r1.FundId

    where r2.fsFundId > r1.fsFundId

    -- return results. Have to substitute nulls for the calendar year ones as not done on websie

    select distinct

    l.idx as RowNumber,

    r.RowType,

    r.Primary_Share_Class,

    @latestrptPerfReportDate as ReportDate,

    r.FundGroupId,

    fg.Name,

    r.FundId,

    -- r.fsFundId,

    l.LipperId,

    r.CurrencyCode,

    r.InceptionDate,

    r.ISIN,

    r.FundName ,

    r.FundShortName

    ,r.ShareClass

    ,coalesce(cast(cast(r.Returns_1mth as decimal(10,2)) as varchar(20)),'-') as Returns_1mth,

    coalesce(cast(cast(r.Returns_3mth as decimal(10,2)) as varchar(20)),'-') as Returns_3mth,

    coalesce(cast(cast(r.Returns_6mth as decimal(10,2)) as varchar(20)),'-') as Returns_6mth,

    coalesce(cast(cast(r.Returns_1Yr as decimal(10,2)) as varchar(20)),'-') as Returns_1Yr,

    coalesce(cast(cast(r.Returns_3Yr as decimal(10,2)) as varchar(20)),'-') as Returns_3Yr,

    coalesce(cast(cast( r.Returns_5Yr as decimal(10,2)) as varchar(20)),'-') as Returns_5Yr,

    coalesce(cast(cast(r.YTD as decimal(10,2)) as varchar(20)),'-') as YTD

    --, 0 as Minus1

    ,coalesce(cast(cast(r.Minus1 as decimal(10,2)) as varchar(20)),'-') as Minus1

    ,coalesce(cast(cast(r.Minus2 as decimal(10,2)) as varchar(20)),'-') as Minus2

    ,coalesce(cast(cast(r.Minus3 as decimal(10,2)) as varchar(20)),'-') as Minus3

    ,coalesce(cast(cast(r.Minus4 as decimal(10,2)) as varchar(20)),'-') as Minus4

    ,coalesce(cast(cast(r.PCT1Y0_MTH as decimal(10,2)) as varchar(20)),'-') as PCT1Y0_MTH

    ,coalesce(cast(cast(r.PCT1Y1_MTH as decimal(10,2)) as varchar(20)),'-') as PCT1Y1_MTH

    ,coalesce(cast(cast(r.PCT1Y2_MTH as decimal(10,2)) as varchar(20)),'-') as PCT1Y2_MTH

    ,coalesce(cast(cast(r.PCT1Y3_MTH as decimal(10,2)) as varchar(20)),'-') as PCT1Y3_MTH

    ,coalesce(cast(cast(r.PCT1Y4_MTH as decimal(10,2)) as varchar(20)),'-') as PCT1Y4_MTH

    from @lipperid l

    join #results r -- inner join as we are guaranteed to ahve them (due to inserting any missing earlier)

    on l.LipperId = r.LipperId

    left join rpt.fundgroup fg

    on r.fundgroupid = fg.id

    order by l.Idx -- if using triplet then will depend on earlier insert into @lipperId being ordered

    END

    GO

    β€œ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

  • Stuff like this has to go:

    INNER JOIN app.fund F

    ON CAST(l.lipperid AS varchar(50))= CAST(f.AccountGr AS varchar(50))

    You're going to have to work through this in really small pieces. There's no magic bullet or quick solution. The fact that views are being used within this is pretty frightening on top of everything else this code is doing wrong. Look to eliminate those too. I agree on using temp tables instead of table variables. I didn't even read enough to see the cursor.

    "The credit belongs to the man who is actually in the arena, whose face is marred by dust and sweat and blood"
    - Theodore Roosevelt

    Author of:
    SQL Server Execution Plans
    SQL Server Query Performance Tuning

  • Thanks everyone.

    I'll work on these suggestions and will post on how it worked out.

    Cheers!!

  • MANY things can cause those est/actual row count differences: table vars, UDFs, functions around columns in where, stuff inside views, mis-matched column data types in joins, mismatched datatypes in parameters to their columns, and yes, even out-of-date statistics. πŸ˜€

    Best,
    Kevin G. Boles
    SQL Server Consultant
    SQL MVP 2007-2012
    TheSQLGuru on googles mail service

  • [font="Comic Sans MS"]One simple improvement would be to avoid DDL in the middle of DML statements.

    Move to the top, before any DML the following:[/font]

    [font="Courier New"]CREATE TABLE #results(...[/font]

    [font="Comic Sans MS"]No, this is not the magic bullet, just something to be avoided. I seem to recall from way back when that defining a new table in the middle of select's caused overhead.[/font]

  • j-1064772 (8/7/2015)


    [font="Comic Sans MS"]One simple improvement would be to avoid DDL in the middle of DML statements.

    Move to the top, before any DML the following:[/font]

    [font="Courier New"]CREATE TABLE #results(...[/font]

    [font="Comic Sans MS"]No, this is not the magic bullet, just something to be avoided. I seem to recall from way back when that defining a new table in the middle of select's caused overhead.[/font]

    Huh??? I don't think there is any truth to this. If there is I would love to see some documentation or article explaining this. Not saying it isn't correct but without something to back it up I disagree.

    _______________________________________________________________

    Need help? Help us help you.

    Read the article at http://www.sqlservercentral.com/articles/Best+Practices/61537/ for best practices on asking questions.

    Need to split a string? Try Jeff Modens splitter http://www.sqlservercentral.com/articles/Tally+Table/72993/.

    Cross Tabs and Pivots, Part 1 – Converting Rows to Columns - http://www.sqlservercentral.com/articles/T-SQL/63681/
    Cross Tabs and Pivots, Part 2 - Dynamic Cross Tabs - http://www.sqlservercentral.com/articles/Crosstab/65048/
    Understanding and Using APPLY (Part 1) - http://www.sqlservercentral.com/articles/APPLY/69953/
    Understanding and Using APPLY (Part 2) - http://www.sqlservercentral.com/articles/APPLY/69954/

  • Sean Lange (8/7/2015)


    j-1064772 (8/7/2015)


    [font="Comic Sans MS"]One simple improvement would be to avoid DDL in the middle of DML statements.

    Move to the top, before any DML the following:[/font]

    [font="Courier New"]CREATE TABLE #results(...[/font]

    [font="Comic Sans MS"]No, this is not the magic bullet, just something to be avoided. I seem to recall from way back when that defining a new table in the middle of select's caused overhead.[/font]

    Huh??? I don't think there is any truth to this. If there is I would love to see some documentation or article explaining this. Not saying it isn't correct but without something to back it up I disagree.

    Well, maybe if you gave the table a dirty name you might end up with dirty data?

    β€œ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

Viewing 15 posts - 1 through 15 (of 23 total)

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