SQL Server Эта хранимая процедура занимает очень много времени, как улучшить - PullRequest
0 голосов
/ 25 августа 2011

Эй, у меня есть следующая хранимая процедура, которая работает пошагово

  1. Выборка всех пользователей, которая не занимает много времени
  2. Затем выполните цикл по всем пользователям и проверьте, сколько шагов онизавершено в тесте

Это второй шаг, который, кажется, занимает много времени, потому что для каждого пользователя я выполняю запрос на выборку для довольно большой таблицы.

С точки зрения размера Существуетоколо 4000 пользователей и 90000 строк в моей истории tblUserQuestionnaire.

Heres 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 ]

1 голос
/ 25 августа 2011

Хорошо, я постараюсь разбить это как можно больше.

1) Временные таблицы довольно медленные, вы бы мгновенно улучшили производительность, используя CTE
2) Курсоры в SQL работают безумно медленно, большая часть этой логики, вероятно, должна перейти на ваш бизнес-уровень.

Первая таблица Temp и связанные с ней DELETE могут быть вашим первым CTE, и вам не нужна вся эта логика, просто приличный набор операций

;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

Это сразу избавляет от необходимости использовать первую временную таблицу, вторую и курсор.

Но главное преимущество здесь, я думаю, заключается в том, что не заполняется много данных, а затем происходит их постепенное удаление. Начните с правильной установки tdata, сначала отфильтровав данные на основе ваших параметров, как я делал в первом CTE выше,

0 голосов
/ 25 августа 2011

Один вероятный виновник - курсор. Попробуйте переписать его как операцию на основе множества. Например:

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
0 голосов
/ 25 августа 2011

Вы, кажется, знаете, что курсор - это ваша проблема. Вы, вероятно, должны превратить его в операции над множествами. Это мой общий подход к преобразованию операций курсора в набор операций

  1. создать временную таблицу, готовую для хранения всех данных
  2. повторно заполняйте временную таблицу данными / вычислениями по мере необходимости
  3. Возьмите временные данные и выполните CRUD на БД по мере необходимости.

Исходя из исходного кода, я предлагаю разбить цикл curso на

  1. Операция установки 1: Получить всех пользователей во временной таблице (в которой есть правильные столбцы для всех данных, которые вы соберете)
  2. Операция установки 2: добавьте @MaxStep и @result в временную таблицу (для всех пользователей)
  3. Операция установки 3: если @programmeId NOT NULL, выполнить правильную операцию установки
  4. Операция установки 4: если следующая переменная = что бы ни выполнялось, операция правильной установки ... промыть и повторить и т.д ...

Из окончательной временной таблицы сделайте правильный выбор.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...