0

I've develop a Stored Procedure that gets data from the table VisitorsInfluences and builds five galaxies with the "most influenced visitors" at the middle of each one, and the visitors that are most influenced by them around them on each galaxy. Is this clear so far?

I don't know why, the Stored Procedure is taking around 6 or 7 or 10 seconds sometimes to run, and it becomes really slow to display on the website.

Could you help me out with this? I don't know what is taking so long, but i know which query is, so I'll comment it below.

Thanks,

Brian

Stored Procedure:

ALTER proc [dbo].[XInfluencedByYCloudGetXml] (@VisitorId int, @VisitorIdLogged int, @mindTopicId varchar(17))
as
set nocount on
declare @threshold decimal(6,2),
        @FirstObj varchar(17),
        @GalaxyId int,
        @MainObjTitle varchar(255),
        @MyMindTopicId varchar(17),
        @CoId varchar(17)

set @threshold = 0.0001
set @GalaxyId = 1

select @MyMindTopicId = topicid from MyMindTopicVisitor where visitorid = @visitoridLogged

declare @MMIV table
(
    VisitorId int not null,
    AuthorId varchar(17) not null,
    MMIV decimal(6,4),
    PRIMARY KEY(VisitorId, AuthorId)
)


declare @Universe table
(
    GalaxyId int,
    VisitorId int,
    CoId varchar(20),
    ObjType varchar(35),
    ObjTitle varchar(255),
    ObjFontSize tinyint,
    ObjPosition tinyint
)

/* Get the most influencing authors for the visitor */
insert into @MMIV
select distinct top 5
    vi.VisitorIdX,
    vi.AuthorIdY,
    vi.[value]
from
    VisitorsInfluences vi
inner join
    tblcontentobjects co on co.coid = vi.authoridy
where
    vi.visitoridx = @visitorid
    and co.visitorid <> @visitorid
    and vi.[value] >= @threshold
group by
    vi.VisitorIdX,
    vi.AuthorIdY,
    vi.[value]
order by
    vi.[value] desc


/* Loop until MMIV is empty */
WHILE (select count(*) from @MMIV) > 0
BEGIN
    select top 1
        @FirstObj = authorid 
    from    
        @MMIV 
    order by 
        MMIV desc

    --Insert the center object in the current galaxy
    /* DEBUG:   En esta query tenes que hacer lo mismo que te dije para la font. Chequear el valor de influencia para el visitorLogged
                y dependiendo de ese valor setear el tamaño de fuente */
    insert into @Universe
    select
        @GalaxyId,
        tco.visitorid,
        @FirstObj,
        'Person',
        tco.firstname + ' ' + tco.lastname,
        case 
            when 
                (
                    select 
                        [value]
                    from
                        visitorsinfluences vi
                    where 
                        vi.visitoridx = @visitorIdLogged 
                        and vi.authoridy = tco.coid
                ) >= 0.66 
                then 5 
                else 
                    case 
                        when 
                            (
                                select 
                                    [value]
                                from
                                    visitorsinfluences vi
                                where 
                                    vi.visitoridx = @visitorIdLogged 
                                    and vi.authoridy = tco.coid
                            ) >= 0.33 
                            then 3 
                            else 1 
                    end 
        end as font,
        4
    from
        @MMIV mm
    inner join
        tblcontentobjects tco on tco.coid = mm.authorid
    where
        mm.authorid = @FirstObj

    delete from @MMIV where authorid = @FirstObj

    --Insert the cluster objects in the current galaxy
    /* DEBUG:   En esta query tenes que hacer lo que te dije para la font. Chequear el valor de influencia para el visitorLogged
                y dependiendo de ese valor setear el tamaño de fuente */
    **insert into @Universe
    select top 5
        @GalaxyId as galaxyid,
        vi.visitoridx as visitoridx,
        co.coid as coid,
        'Person' as cotype,
        co.firstname + ' ' + co.lastname as visitorname,
        case 
            when 
                (
                    select 
                        [value]
                    from
                        visitorsinfluences vi
                    where 
                        vi.visitoridx = @visitorIdLogged 
                        and vi.authoridy = co.coid
                ) >= 0.66 
                then 5 
                else 
                    case 
                        when 
                            (
                                select 
                                    [value]
                                from
                                    visitorsinfluences vi
                                where 
                                    vi.visitoridx = @visitorIdLogged 
                                    and vi.authoridy = co.coid
                            ) >= 0.33 
                            then 3 
                            else 1 
                    end 
        end as font,
        case when vi.[value] >= 0.66 then 3 else case when vi.[value] >= 0.33 then 2 else 1 end end as position
    from
        VisitorsInfluences vi   
    inner join 
        tblcontentobjects co on vi.visitoridx = co.visitorid
    left join 
        @universe u on u.coid = co.coid
    left join
        @mmiv mm on mm.authorid = co.coid
    where
        vi.authoridy = @FirstObj
        and [value] >= convert(real,@threshold)
        and vi.visitoridx <> @visitorid
        --and vi.visitoridx not in (select visitorid from @Universe)
        --and co.coid not in (select coid from @Universe)
        --and co.coid not in (select authorid from @mmiv)       
        and u.coid is null
        and mm.authorid is null
        and u.visitorid is null
    /*group by
        vi.visitoridx,
        co.coid,
        v.firstname,
        v.lastname,
        case when vi.[value] = 1 then 5 else case when vi.[value] >= (@threshold / 2) then 3 else 1 end end,
        case when vi.[value] >= 0.66 then 3 else case when vi.[value] >= 0.33 then 2 else 1 end end*/
    order by
        vi.[value] desc**




    if ((select count(*) from @Universe where GalaxyId = @GalaxyId) = 1) and @GalaxyId <= 5
    begin
        insert into @Universe
        select top 5
            @GalaxyId as galaxyid,
--              convert(varchar, vi.visitoridx) as visitoridx,
            vi.visitoridx as visitoridx,
            co.coid as coid,
            'Person' as cotype,
            v.firstname + ' ' + v.lastname as visitorname,
            0 as font,
            case when vi.[value] >= 0.66 then 3 else case when vi.[value] >= 0.33 then 2 else 1 end end as position
        from
            VisitorsInfluences vi
        inner join
            tblvisitor v on v.visitorid = vi.visitoridx
        inner join 
            tblcontentobjects co on v.visitorid = co.visitorid
        left join
            @universe u on u.coid = co.coid
        left join
            @mmiv mm on mm.authorid = co.coid
        where
            vi.authoridy=@FirstObj-- and vi.visitoridx = v.visitorid
            and vi.visitoridx <> @visitorid
--              and convert(varchar, vi.visitoridx) not in (select objid from @Universe)
            --and vi.visitoridx not in (select visitorid from @Universe)
            --and co.coid not in (select coid from @Universe)
            --and co.coid not in (select authorid from @mmiv)
            and u.coid is null
            and mm.authorid is null
            and u.visitorid is null
        order by
            vi.[value] desc
    end

--  delete from @MMIV where authorid in (select ObjId from @Universe)
    delete from @MMIV where visitorid in (select visitorid from @Universe)

    set @GalaxyId = @GalaxyId + 1
END

--Getting the XML output

select 
    @MainObjTitle = rtrim(ltrim(firstname)) + ' ' + ltrim(rtrim(lastname))
from 
    tblcontentobjects
where 
    visitorid = @visitorid

select 
    @CoId = co.coid
from
    tblcontentobjects co
where
    co.visitorid = @visitorid


SELECT
    @MainObjTitle as '@MainObjTitle',
    @CoId as '@CoId',
    (
        SELECT
            s.GalaxyID AS [@Id],
            (
                SELECT
                    U.VisitorId AS [@VisitorId],
                    U.CoId AS [@CoId],
                    U.ObjType AS [@Type],
                    U.ObjTitle AS [Title],
                    U.ObjFontSize as [FontSize],
                    U.ObjPosition as [Position],
                    co.[role] as [Role],
                    co.Affiliation as [Org],
                    case when ctr.topicid is null then 0 else 1 end as [IsInMyMind],
                    isnull(imgs.coviewurllink, '') as [coPicture],
                    case 
                        when co.visitorid is null then ''
                        when exists (
                                        select  *
                                        from    visitorrequests vrs
                                        where   vrs.RequestDate > dateadd(mi, -10, getdate()) and
                                                vrs.visitorid = co.visitorid
                                    ) then '_online'
                            else '_offline'
                    end as [IsOnline],
                    case when mctr.topicid is null then 0 else 1 end as [HasSocialNetworkProfile]
                FROM
                    @Universe AS U
                inner join  tblcontentobjects co 
--              on          convert(varchar, co.visitorid) = U.ObjId or co.coid = U.ObjId
                on          co.visitorid = U.VisitorId or co.coid = U.CoId
                inner join  tblvisitor v
                on          v.visitorid = co.visitorid
                left join
                (
                    select  img.*, cir.coidb
                    from    tblcointerrelations cir
                    inner join tblcontentobjects img
                    on      img.coid = cir.coida
                    where   img.cotype='images'
                ) imgs
                on          co.coid = imgs.coidb
                left join   tblCoTopicRelations ctr
                on          ctr.coid = co.coid and ctr.topicid = @MyMindTopicId
                left join   tblCoTopicRelations mctr
                on          mctr.coid = co.coid and mctr.topicid = @mindTopicId
                WHERE       
                    U.GalaxyID = s.GalaxyID
                ORDER BY
                    U.ObjPosition DESC
                FOR XML PATH('Object'), TYPE
            )
        FROM
            (
                SELECT      GalaxyID
                FROM        @Universe
                GROUP BY    GalaxyID
            ) AS s
        ORDER BY
            GalaxyId
        FOR XML PATH('Galaxy'), TYPE
    )
FOR XML PATH('Universe')`
Preet Sangha
  • 64,563
  • 18
  • 145
  • 216
Brian Roisentul
  • 4,590
  • 8
  • 53
  • 81
  • 1
    You need to first use SQL Server profiler to find out which part of the SP is slow. Once you have this post that information please – Preet Sangha Sep 04 '09 at 01:12
  • Well, its hard to tell as the SP has a lot of content. You could try to figure exactly which part is the slowest by printing GETDATE() in some places and check where most time in the SP is consumed. – MaxiWheat Sep 04 '09 at 01:18
  • I am curious if you have problems with missing some indexes. As Preet suggested, profile it, but I think you may want to break your problem down into two parts. First, find a fast way to build the five galaxies, then, as a separate call, build up the info for each galaxy. – James Black Sep 04 '09 at 01:21
  • Could you please also provide the table definition, including the keys and any indexes? thnx. – RBarryYoung Sep 04 '09 at 01:41
  • Here is the profiler info, I don't know if you need some extra info: SQL:BatchCompleted XInfluencedByYCloudGetXml 382, 382, 'T92906849551' CPU: 4859 Reads: 133177 Writes: 0 Duration: 5395 SPID: 57 StartTime: 2009-09-04 11:31:03.427 EndTime: 2009-09-04 11:31:08.833 – Brian Roisentul Sep 04 '09 at 14:34

3 Answers3

4

There seem to be many things to choose from to explain why your stored procedure is slow. Here are some thoughts.

  • Execution plan cache. The proc is one big procedural process. The first time it runs it will store the execution plan based on the initial parameters. Depending on your table sizes and indexes the plan first cached might not be good for other scenarios
  • Table variables. You chose to use table variables (start with @ and exist in memory) over temp tables (start with # and exist on disk). This may be good or bad. It depends on your conditions but can hurt if the wrong one is chosen
  • Order By. You use them frequently and sorting has a cost.
  • WHILE loop. A very procedural approach. You're using SQL so you should be thinking in a set-based environment
  • Correlated sub-queries. For each row in the outer set the correlated sub-query has to run. That will add up.
  • You do a data type conversion in the WHERE clause ([value] >= convert(real,@threshold)). This is a performance hit.
  • The @universe table variable has no primary key, index, or cluster
  • You have a 4 level nested correlated sub-query under a case statement that ultimately gets converted to XML 3 times
  • Overall...your stored procedure does too much. Offload this work in advance somehow. Pre-calculate things. Add indexes. You're concerned about the reporting performance so design your schema around reporting and the fastest response times possible.

You need to review the Execution plan and look for the high costs. Make small changes to the procedure and compare the execution plans of the before and after to see how your changes impact performance. Make benchmarks. Know where you start and you'll be able to measure how far you've gone.

Run this statement and then run the procedure.
SET STATISTICS IO ON GO Look at the results and for every table that is touched look at the "logical reads". The ones with the highest logical reads need your attention. They can be loosely translated into the need for indexes on that object.

Eric Sabine
  • 1,165
  • 7
  • 8
  • +1 for the comprehensive list of issues. Instead of a WHILE loop, you might want to use one or several common table expressions, which can execute recursively too (so that this may completely replace your @MMIV table). – Lucero Sep 04 '09 at 13:58
  • I'm sorry, I didn't get your point. Could you explain me a little more what you mean? – Brian Roisentul Sep 04 '09 at 15:10
2

First of all I'd lose:

while (select count(*) from @mmiv) > 0

And replace it with:

declare @temp_count int
select @temp_count = count(*) from @mmiv

while @temp_count > 0

And this below wherever you delete records from @mmiv:

select @temp_count = (@temp_count - 1)

This will already reduce overhead since you're not doing a count() every time you looped through the sequence. You may also consider putting some of your selects with multiple joins into views, here is why.

Community
  • 1
  • 1
Mr. Smith
  • 5,489
  • 11
  • 44
  • 60
0

Thanks all of you for your answers.

I've finally solved it. The problem was lack of indexes(on the value field for example), now it works great.

Thanks,

Brian

Brian Roisentul
  • 4,590
  • 8
  • 53
  • 81
  • Have a look on the execution plan. If you find table scans instead of index usage, those may be candidates for indexed views (or a different approach to solve the issue may be even better). – Lucero Sep 04 '09 at 14:01
  • I did that some days ago, and the only scan that I find is a Clustered Index Scan...and the only table scan is on the variable table @Universe, but it costs 0 % so...i don't think that's causing problems. – Brian Roisentul Sep 04 '09 at 14:25