Something that has helped us make considerable performance improvements in Arena is the use of Common Table Expressions.  Common Table Expressions are especially useful when we need a list of hierarchal data.  For example below are two stored procedures that were previously used to get a list of tag members in a hierarchal list of tags (this procedures is used by the Volunteer Administration page under Volunteer Tracking).

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[core_sp_get_profile_member_Details]') AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[core_sp_get_profile_member_Details]

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE                       PROC dbo.core_sp_get_profile_member_Details
@OrganizationID int,
@ProfileType int,
@OwnerID int,
@FirstName varchar(40),
@LastName varchar(40),
@ParentProfileID int

AS

CREATE TABLE #ChildProfiles(
    [profile_id] [int] NOT NULL)

-- Declare a cursor for determinining the groups and members in any child profiles
DECLARE @ProfileID int
DECLARE @ProfileOwnerID int

DECLARE ChildProfile CURSOR LOCAL FOR
    SELECT     profile_id,
        owner_id
    FROM core_profile
    WHERE organization_id = @OrganizationID
    AND profile_type = @ProfileType
    AND @ParentProfileID = -1 OR (parent_profile_id = @ParentProfileID OR profile_id = @ParentProfileID)

-- Open the cursor
OPEN ChildProfile

-- Fetch the first record
FETCH NEXT FROM ChildProfile
    INTO     @ProfileID,
        @ProfileOwnerID

-- Loop through all the records
WHILE @@FETCH_STATUS = 0
BEGIN

    IF @ProfileType <> 0 OR @ProfileOwnerID = @OwnerID
    BEGIN

        INSERT INTO #ChildProfiles
        VALUES (@ProfileID)

        -- For each child profile, call this stored procedure recursively to recurse down
        -- all child profiles.
        DECLARE @RC int
        EXEC @RC = core_sp_get_profile_member_Details_Recurse @ProfileID

    END

    -- Read the next record
    FETCH NEXT FROM ChildProfile
        INTO     @ProfileID,
            @ProfileOwnerID

END

-- Close and deallocate the cursor
CLOSE ChildProfile
DEALLOCATE ChildProfile

SELECT     *
FROM core_v_profile_memberList
WHERE profile_id in (SELECT profile_id FROM #ChildProfiles)
AND (nick_name like @FirstName + '%' OR first_name like @FirstName + '%')
AND (last_name like @LastName + '%')
ORDER BY person_name

DROP TABLE #ChildProfiles

 

USE [Arena]
GO
/****** Object:  StoredProcedure [dbo].[core_sp_get_profile_member_Details_Recurse]    Script Date: 04/29/2008 19:05:32 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER                  PROC [dbo].[core_sp_get_profile_member_Details_Recurse]
@ParentProfileID int

AS

-- Declare a cursor for determinining the groups and members in any child profiles
DECLARE @ProfileID int

DECLARE ChildProfile CURSOR LOCAL FOR
    SELECT     profile_id
    FROM core_profile
    WHERE isnull(parent_profile_id, -1) = @ParentProfileID

-- Open the cursor
OPEN ChildProfile

-- Fetch the first record
FETCH NEXT FROM ChildProfile
    INTO     @ProfileID

-- Loop through all the records
WHILE @@FETCH_STATUS = 0
BEGIN

    INSERT INTO #ChildProfiles
    VALUES (@ProfileID)

    -- For each child profile, call this stored procedure recursively to recurse down
    -- all child profiles.
    DECLARE @RC int
    EXEC @RC = core_sp_get_profile_member_Details_Recurse @ProfileID

    -- Read the next record
    FETCH NEXT FROM ChildProfile
        INTO     @ProfileID

END

-- Close and deallocate the cursor
CLOSE ChildProfile
DEALLOCATE ChildProfile

You can see in these stored procedures that we were using a temporary table, a cursor, and then calling another stored procedure recursively, all to get a list of child tags in a hierarchy.  With Common Table Expressions we were able to remove all of this.  Below is the new procedure…

USE [Arena]
GO
/****** Object:  StoredProcedure [dbo].[core_sp_get_profile_member_Details]    Script Date: 04/29/2008 18:57:30 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER                       PROC [dbo].[core_sp_get_profile_member_Details]
@OrganizationID int,
@ProfileType int,
@OwnerID int,
@FirstName varchar(40),
@LastName varchar(40),
@ParentProfileID int,
@Status varchar(500)

AS

    WITH cte_profiles (
        [profile_id] )
    AS
    (
        SELECT
           P.[profile_id]
        FROM core_profile P
        WHERE (ISNULL(P.parent_profile_id,-1) = @ParentProfileID OR P.profile_id = @ParentProfileID)
        AND P.organization_id = @OrganizationID
        AND P.profile_type = @ProfileType
        AND (P.profile_type <> 0 OR p.owner_id = @OwnerID)

        UNION ALL

        SELECT
           P.[profile_id]
        FROM core_profile P
        INNER JOIN cte_profiles CTE ON CTE.profile_id = ISNULL(P.parent_profile_id,-1)
        WHERE P.organization_id = @OrganizationID
        AND P.profile_type = @ProfileType
        AND (P.profile_type <> 0 OR p.owner_id = @OwnerID)
    )

    SELECT ML.*
    FROM cte_profiles P
    INNER JOIN core_profile_member PM
        ON PM.profile_id = P.profile_id
    INNER JOIN core_v_profile_memberList ML
        ON ML.profile_id = PM.profile_id
        AND ML.person_id = PM.person_id
    WHERE (ML.nick_name like @FirstName + '%' OR ML.first_name like @FirstName + '%')
    AND (ML.last_name like @LastName + '%')
    AND CAST(PM.status_luid as varchar) in ( SELECT item FROM dbo.fnSplit(@Status)  )
    ORDER BY ML.last_name, ML.nick_name

The new procedure does a recursive union within the Common Table Expression.  Using this method is incredibly faster than the original cursor.

Leave a Reply

You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>