SQL Server这个存储过程耗时很长,如何改进

时间:2011-08-25 08:40:29

标签: sql-server sql-server-2005

嘿我有以下存储过程,它按步骤

运行
  1. 获取所有不花费太多时间的用户
  2. 然后遍历所有用户并检查他们在测试中完成了多少步骤
  3. 它的第二步似乎需要很长时间,因为对于每个用户,我在一个非常大的表上执行选择查询。

    就尺寸而言 我的tblUserQuestionnaireHistory中有大约4000个用户和90000行。

    继承人SP

        ALTER PROCEDURE [spGetStoreTrainingSummary_Test]
    (
        @staffId INT = default,
        @storeTypeId INT = default,
        @storeId INT = default,
        @county VARCHAR(50) = default,
        @programmeId INT = default,
        @profileId INT = default,
        @showNulls INT = default,
        @position VARCHAR(50) = default,
        @roaId INT = default
    )
    AS
    BEGIN
    
    SET NOCOUNT ON;
    
    -- Place all users inner join stores into a temp table
    CREATE TABLE #TempMainTable(
        [id] INT,
        [profileId] INT,
        [position] VARCHAR(50),
        [storeId] INT,
        [county] VARCHAR(50),
        [storeTypeId] INT,
        [roaId] INT
    )
    
    INSERT  #TempMainTable
    SELECT  tblUsers.id, tblUsers.profileId, tblUsers.position, tblStores.id as storeId, tblStores.county, tblStores.storeTypeId, tblStores.roaID
    FROM    tblUsers INNER JOIN tblStores ON tblUsers.storeId = tblStores.id
    WHERE   (tblUsers.statusId = 1) AND (tblStores.statusId = 1)
    
    IF @profileId > 0 --## Filter by profile
        BEGIN
            DELETE FROM #TempMainTable WHERE profileId <> @profileId
        END
    
    IF len(@position) > 0 --## Filter by position
        BEGIN
            DELETE FROM #TempMainTable WHERE position <> @position
        END
    
    IF @storeId > 0 --## Filter by store
        BEGIN
            DELETE FROM #TempMainTable WHERE storeId <> @storeId
        END
    
    IF len(@county) > 0 --## Filter by county
        BEGIN
            DELETE FROM #TempMainTable WHERE county <> @county
        END
    
    IF @storeTypeId > 0 --## Filter by storeTypeId
        BEGIN
            DELETE FROM #TempMainTable WHERE storeTypeId <> @storeTypeId
        END
    
    IF @roaId > 0 --## Filter by roaId
        BEGIN
            DELETE FROM #TempMainTable WHERE roaId <> @roaId
        END
    -- SELECT * FROM #TempMainTable
    
    CREATE TABLE #TempTable(
        [userId] INT,
        [menuName] varchar(250),
        [stepId] int,
        [programmeId] int,
        [result] varchar(250)
    )
    
    DECLARE @UserId INT
    DECLARE @MaxStep INT
    DECLARE @Result VARCHAR(100)
    DECLARE @UserList CURSOR
    
    SET @UserList = CURSOR FOR
    SELECT  id
    FROM    #TempMainTable
    GROUP BY id
    
    OPEN @UserList
    
    FETCH NEXT FROM @UserList INTO @UserId
    WHILE (@@FETCH_STATUS = 0)  
    BEGIN
            --## Staff Induction Programme
            SET @MaxStep = (SELECT  MAX(stepId) AS maxId FROM tblUserQuestionnaireHistory WHERE (userId = @UserId) AND (programmeId = 13) AND (success = 1))
            SET @Result = (SELECT CASE WHEN @MaxStep = 9 THEN 'Passed' WHEN @MaxStep <> 9 THEN 'Step ' + + CAST(@MaxStep AS VARCHAR(10)) + ' completed out of 9' ELSE 'No steps completed yet' END as Result)
            INSERT #TempTable
            SELECT @UserId, 'Staff Induction Programme &copy;', @MaxStep, 13, @Result
        --PRINT @UserId
            --PRINT @MaxStep
            --PRINT @Result
    
    FETCH NEXT FROM @UserList INTO @UserId
    END 
    CLOSE @UserList
    DEALLOCATE @UserList
    DROP TABLE #TempMainTable
    
    --## Filter by programme id
    IF @programmeId IS NOT NULL
    BEGIN
        DELETE FROM #TempTable WHERE programmeId <> @programmeId
    END
    
    IF @showNulls = 1 -- Select All Records
    BEGIN
        SELECT #TempTable.*, tblUsers.firstName + ' ' + tblUsers.lastName AS fullname, tblUsers.firstName, tblUsers.lastName, tblStores.name AS storeName, tblStores.county, tblStore_Types.name AS storeType, tblUsers.position, tblStores.surname + ' ' + tblStores.name as retailerName, tblProfiles.name as profile, tblRoa.lastname + ' ' + tblRoa.firstname as roa
        FROM #TempTable INNER JOIN tblUsers ON #TempTable.userId = tblUsers.id INNER JOIN tblStores ON tblUsers.storeId = tblStores.id INNER JOIN tblStore_Types ON tblStores.storeTypeId = tblStore_Types.id INNER JOIN tblProfiles ON tblUsers.profileId = tblProfiles.id INNER JOIN tblROA ON tblStores.roaID = tblROA.id
    END
    ELSE IF @showNulls = 2 -- Select Users who have sat at least one training
    BEGIN
        SELECT #TempTable.*, tblUsers.firstName + ' ' + tblUsers.lastName AS fullname, tblUsers.firstName, tblUsers.lastName, tblStores.name AS storeName, tblStores.county, tblStore_Types.name AS storeType, tblUsers.position, tblStores.surname + ' ' + tblStores.name as retailerName, tblProfiles.name as profile, tblRoa.lastname + ' ' + tblRoa.firstname as roa 
        FROM #TempTable INNER JOIN tblUsers ON #TempTable.userId = tblUsers.id INNER JOIN tblStores ON tblUsers.storeId = tblStores.id INNER JOIN tblStore_Types ON tblStores.storeTypeId = tblStore_Types.id INNER JOIN tblProfiles ON tblUsers.profileId = tblProfiles.id INNER JOIN tblROA ON tblStores.roaID = tblROA.id
        WHERE userId IN (SELECT userId FROM #TempTable WHERE (stepId IS NOT NULL) GROUP BY userId)
    END
    ELSE -- Select Only Training records that have been sat
    BEGIN 
        SELECT #TempTable.*, tblUsers.firstName + ' ' + tblUsers.lastName AS fullname, tblUsers.firstName, tblUsers.lastName, tblStores.name AS storeName, tblStores.county, tblStore_Types.name AS storeType, tblUsers.position, tblStores.surname + ' ' + tblStores.name as retailerName, tblProfiles.name as profile, tblRoa.lastname + ' ' + tblRoa.firstname as roa
        FROM #TempTable INNER JOIN tblUsers ON #TempTable.userId = tblUsers.id INNER JOIN tblStores ON tblUsers.storeId = tblStores.id INNER JOIN tblStore_Types ON tblStores.storeTypeId = tblStore_Types.id INNER JOIN tblProfiles ON tblUsers.profileId = tblProfiles.id INNER JOIN tblROA ON tblStores.roaID = tblROA.id 
        WHERE (stepId IS NOT NULL)
    END
    
    END
    

    有关如何批准此存储过程的任何提示?

    编辑显示最新的SP:

        ALTER PROCEDURE [u1017987_dbase_user].[spGetStoreTrainingSummary_Test]
    (
        @staffId INT = default,
        @storeTypeId INT = default,
        @storeId INT = default,
        @county VARCHAR(50) = default,
        @programmeId INT = default,
        @profileId INT = default,
        @showNulls INT = default,
        @position VARCHAR(50) = default,
        @roaId INT = default
    )
    AS
    BEGIN
    
    SET NOCOUNT ON;
    
    -- Place all users inner join stores into a temp table
    CREATE TABLE #TempMainTable(
        [id] INT,
        [profileId] INT,
        [position] VARCHAR(50),
        [storeId] INT,
        [county] VARCHAR(50),
        [storeTypeId] INT,
        [roaId] INT
    )
    
    
    
    CREATE TABLE #TempTable(
        [userId] INT,
        [menuName] varchar(250),
        [stepId] int,
        [programmeId] int,
        [result] varchar(250)
    )
    
    DECLARE @UserId INT
    DECLARE @MaxStep INT
    DECLARE @Result VARCHAR(100)
    DECLARE @UserList CURSOR
    
    ;WITH tempMainTable
    AS
    (
      SELECT  tblUsers.id, tblUsers.profileId, tblUsers.position, tblStores.id as storeId, 
      tblStores.county, tblStores.storeTypeId, tblStores.roaID
      FROM    tblUsers INNER JOIN tblStores ON tblUsers.storeId = tblStores.id
      WHERE   (tblUsers.statusId = 1) AND (tblStores.statusId = 1)
      AND (@profileId = 0 OR profileId = @profileId)
      AND (len(@position) = 0 OR position = @position)
      AND (@storeId = 0 OR storeId = @storeId)
      AND (len(@county) = 0 OR county = @county)
      AND (@storeTypeId = 0 OR storeTypeId = @storeTypeId)
      AND (@roaId = 0 OR roaId = @roaId)
    ),
    tempTable AS
    (
        SELECT tempMainTable.userId,
               'Staff Induction Programme &copy;',
               (SELECT  MAX(stepId) AS maxId FROM tblUserQuestionnaireHistory WHERE (userId = tempMainTable.userId) AND (programmeId = 13) AND (success = 1)),
               13,
               (SELECT CASE (SELECT  MAX(stepId) AS maxId FROM tblUserQuestionnaireHistory WHERE (userId = tempMainTable.userId) AND (programmeId = 13) AND (success = 1)) WHEN  9 THEN 'Passed' ELSE 'Step ' + + CAST(@MaxStep AS VARCHAR(10)) + ' completed out of 9'  END as Result)    
        FROM tempMainTable
        WHERE (@programmeId IS NULL OR @programmeId=13)
    )
    
    --## Filter by programme id
    IF @programmeId IS NOT NULL
    BEGIN
        DELETE FROM #TempTable WHERE programmeId <> @programmeId
    END
    
    IF @showNulls = 1 -- Select All Records
    BEGIN
        SELECT #TempTable.*, tblUsers.firstName + ' ' + tblUsers.lastName AS fullname, tblUsers.firstName, tblUsers.lastName, tblStores.name AS storeName, tblStores.county, tblStore_Types.name AS storeType, tblUsers.position, tblStores.surname + ' ' + tblStores.name as retailerName, tblProfiles.name as profile, tblRoa.lastname + ' ' + tblRoa.firstname as roa
        FROM #TempTable INNER JOIN tblUsers ON #TempTable.userId = tblUsers.id INNER JOIN tblStores ON tblUsers.storeId = tblStores.id INNER JOIN tblStore_Types ON tblStores.storeTypeId = tblStore_Types.id INNER JOIN tblProfiles ON tblUsers.profileId = tblProfiles.id INNER JOIN tblROA ON tblStores.roaID = tblROA.id
    END
    ELSE IF @showNulls = 2 -- Select Users who have sat at least one training
    BEGIN
        SELECT #TempTable.*, tblUsers.firstName + ' ' + tblUsers.lastName AS fullname, tblUsers.firstName, tblUsers.lastName, tblStores.name AS storeName, tblStores.county, tblStore_Types.name AS storeType, tblUsers.position, tblStores.surname + ' ' + tblStores.name as retailerName, tblProfiles.name as profile, tblRoa.lastname + ' ' + tblRoa.firstname as roa 
        FROM #TempTable INNER JOIN tblUsers ON #TempTable.userId = tblUsers.id INNER JOIN tblStores ON tblUsers.storeId = tblStores.id INNER JOIN tblStore_Types ON tblStores.storeTypeId = tblStore_Types.id INNER JOIN tblProfiles ON tblUsers.profileId = tblProfiles.id INNER JOIN tblROA ON tblStores.roaID = tblROA.id
        WHERE userId IN (SELECT userId FROM #TempTable WHERE (stepId IS NOT NULL) GROUP BY userId)
    END
    ELSE -- Select Only Training records that have been sat
    BEGIN 
        SELECT #TempTable.*, tblUsers.firstName + ' ' + tblUsers.lastName AS fullname, tblUsers.firstName, tblUsers.lastName, tblStores.name AS storeName, tblStores.county, tblStore_Types.name AS storeType, tblUsers.position, tblStores.surname + ' ' + tblStores.name as retailerName, tblProfiles.name as profile, tblRoa.lastname + ' ' + tblRoa.firstname as roa
        FROM #TempTable INNER JOIN tblUsers ON #TempTable.userId = tblUsers.id INNER JOIN tblStores ON tblUsers.storeId = tblStores.id INNER JOIN tblStore_Types ON tblStores.storeTypeId = tblStore_Types.id INNER JOIN tblProfiles ON tblUsers.profileId = tblProfiles.id INNER JOIN tblROA ON tblStores.roaID = tblROA.id 
        WHERE (stepId IS NOT NULL)
    END
    
    END
    

3 个答案:

答案 0 :(得分:1)

好的,我会尝试尽可能地解决这个问题。

1)临时表非常慢,你可以使用CTE来获得更好的性能 2)SQL中的游标非常慢,大部分逻辑可能会进入您的业务层。

第一个Temp表和相关的DELETE可能是你的第一个CTE,你不需要所有的逻辑,只是一个体面的设置op

;WITH tempMainTable
AS
(
  SELECT  tblUsers.id, tblUsers.profileId, tblUsers.position, tblStores.id as storeId, 
  tblStores.county, tblStores.storeTypeId, tblStores.roaID
  FROM    tblUsers INNER JOIN tblStores ON tblUsers.storeId = tblStores.id
  WHERE   (tblUsers.statusId = 1) AND (tblStores.statusId = 1)
  AND (@profileId = 0 OR profileId = @profileId)
  AND (len(@position) = 0 OR position = @position)
  AND (@storeId = 0 OR storeId = @storeId)
  AND (len(@county) = 0 OR county = @country)
  AND (@storeTypeId = 0 OR storeTypeId = @storeTypeId)
  AND (@roaId = 0 OR roaId = @roaId)
),
tempTable AS
(
    SELECT tempMainTable.userId,
           'Staff Induction Programme &copy;',
           (SELECT  MAX(stepId) AS maxId FROM tblUserQuestionnaireHistory WHERE (userId = tempMainTable.userId) AND (programmeId = 13) AND (success = 1)),
           13,
           (SELECT CASE (SELECT  MAX(stepId) AS maxId FROM tblUserQuestionnaireHistory WHERE (userId = tempMainTable.userId) AND (programmeId = 13) AND (success = 1)) WHEN  9 THEN 'Passed' WHEN ELSE 'Step ' + + CAST(@MaxStep AS VARCHAR(10)) + ' completed out of 9'  END as Result)    
    FROM tempMainTable
    WHERE (@programmeId IS NULL OR @programmeId=13)
)
// do the rest here

立即消除了对第一个临时表,第二个和光标的需要。

但我认为这里的主要优点是不填充大量数据,然后逐步删除它。首先根据您的参数过滤数据来开始设置rt tdata,就像我在上面的第一个CTE中所做的那样,

答案 1 :(得分:0)

您似乎知道光标是您的问题。您可能需要将其转换为设置操作。 这是我将光标操作转换为set ops的一般方法

  1. 创建一个准备好保存所有数据的临时表
  2. 根据需要使用数据/计算重复填充临时表
  3. 获取临时数据并根据需要在数据库上执行CRUD。
  4. 从源代码我的建议是将curso循环分解为

    1. 设置操作1:让所有用户进入临时表(对于您将收集的所有数据都有正确的列)
    2. 设置操作2:将@MaxStep和@result添加到临时表(对于al用户)
    3. 设置操作3:如果@programmeId IS NOT NULL,则执行正确的设置操作
    4. 设置操作4:如果下一个变量=正确的设置操作是什么 ......冲洗并重复......
    5. 从最终的临时表中,进行正确的选择。

答案 2 :(得分:0)

一个可能的罪魁祸首是光标。尝试将其重写为基于集合的操作。例如:

INSERT  #TempTable
SELECT  tmt.id
,       'Staff Induction Programme &copy;'
,       uqh.MaxStep
,       13
,       CASE 
        WHEN uqh.MaxStep = 9 THEN 'Passed' 
        WHEN uqh.MaxStep <> 9 THEN 'Step ' + cast(uqh.MaxStep AS VARCHAR(10)) + 
                                   ' completed out of 9' 
        ELSE 'No steps completed yet' 
        END
FROM    #TempMainTable tmt
LEFT JOIN
        (
        SELECT  uqh.userId
        ,       MAX(uqh.stepId) as MaxStep
        FROM    tblUserQuestionnaireHistory uqh 
        WHERE   uqh.programmeId = 13 
                AND uqh.success = 1
        GROUP BY
                uqh.userId
        ) uqh
on      uqh.userId = tmt.id