Оптимизация хранимой процедуры, использующей курсор - PullRequest
1 голос
/ 07 октября 2019

Существует длительная хранимая процедура. 2-я таблица (посещение) используется только для результатов, которые будут использоваться в предложении where при извлечении записей из первой таблицы (утверждения).

Ниже приведен DDL двух задействованных таблиц и сценарий хранимой процедуры. По-видимому, поскольку это просто образец, он не отображает никаких записей;таким образом, при данных выборки не наблюдается узких мест. Но представьте, что в курсоре много userr_ids. Существует также дополнительная стоимость, так как она вызывает другую хранимую процедуру. Есть ли способ оптимизировать запрос?

DDL таблиц

 CREATE TABLE [dbo].[Approvals](
    userr_id [int] NOT NULL,
    appro_id [int] NOT NULL,
    app_units [decimal](9, 2) NOT NULL,
    c_units [tinyint] NOT NULL,
    usedunits [decimal](9, 2) NOT NULL,
    deleted [bit] NOT NULL
) 

INSERT INTO approvals 
VALUES
(4262,  29, 36.00,  1,  0.00,   0),
(1717,  30, 24.00,  1,  0.00,   0),
(4743,  31, 76.00,  1,  0.00,   0),
(4460,  33, 36.00,  1,  0.00,   0),
(4488,  35, 36.00,  1,  0.00,   0),
(3871,  36, 24.00,  1,  0.00,   0),
(3561,  37, 12.00,  1,  3.00,   0),
(4828,  38, 36.00,  1,  0.00,   0),
(3828,  39, 24.00,  1,  0.00,   0),
(4101,  40, 24.00,  1,  0.00,   0)

CREATE TABLE [dbo].[Visit](
    userr_id [int] NULL,
    appro_id [int] NULL,
    c_secondary [bit] NULL,
    auth_exceeded [bit] NOT NULL,
    tperiod [int] NOT NULL,
    contratrate [decimal](9, 2) NOT NULL
) 

INSERT INTO visit 
VALUES
(5329,  NULL,   0, 0,   419,    0.00),
(4262,  NULL,   0, 0,   419,    0.00),
(5244,  NULL,   0, 0, 419,  0.00),
(4205,  NULL,   0, 0,   419,    0.00),
(4828,  NULL,   0, 0,   419,    0.00),
(5531,  NULL,   0,0,    419,    0.00),
(5558,  NULL,   0,  0, 419, 0.00),
(4460,  NULL,   0,  0, 419, 0.00),
(5547,  NULL,   0,  0, 419, 0.00),
(5219,  NULL,   0,  0, 419, 0.00)

Хранимая процедура:

CREATE PROCEDURE [dbo].[stored_procedure] 
AS 

DECLARE @userr_id Int, @cnt Int
SET @cnt = 0

DECLARE c CURSOR FOR


    SELECT DISTINCT userr_id 
      FROM (SELECT userr_id, app_units, c_units, usedunits,
                   (SELECT count(*) FROM Visit WHERE c_secondary = 0 and appro_id = Approvals.appro_id and auth_exceeded = 0) AS count_notexceeded,
                   (SELECT count(*) FROM Visit WHERE c_secondary = 0 and appro_id = Approvals.appro_id ) AS count_all,
                   (SELECT SUM(tperiod) / 60.00 FROM Visit WHERE c_secondary = 0 and appro_id = Approvals.appro_id ) AS th_all,
                   (SELECT SUM(tperiod) / 60.00 FROM Visit WHERE c_secondary = 0 and appro_id = Approvals.appro_id and auth_exceeded = 0) AS th_notexceeded,
                   (SELECT SUM(contratrate) FROM Visit WHERE c_secondary = 0 and appro_id = Approvals.appro_id and auth_exceeded = 0) AS tr_notexceeded,
                   (SELECT SUM(contratrate) FROM Visit WHERE c_secondary = 0 and appro_id = Approvals.appro_id ) AS tr_all
              FROM Approvals where deleted = 0) t
     WHERE ((c_units = 0 and (count_all <> usedunits or count_notexceeded > app_units))
        OR  (c_units = 2 and (th_all <> usedunits or th_notexceeded > app_units))
        OR  (c_units = 3 and (tr_all <> usedunits or tr_notexceeded > app_units)))

 OPEN c
FETCH NEXT FROM c INTO @userr_id
WHILE @@fetch_status = 0
BEGIN
  SET @cnt = @cnt + 1

  EXEC [_name_of_another_stored_procedure] @userr_id

  FETCH NEXT FROM c INTO @userr_id
END
CLOSE c
DEALLOCATE c

Спасибо. Любое руководство очень ценится.

1 Ответ

3 голосов
/ 07 октября 2019

Попробуйте заменить запрос курсора чем-то вроде этого, который, надеюсь, не будет сканировать несколько раз:

with t as
(
    SELECT userr_id, app_units, c_units, usedunits,
        sum (case when auth_exceeded = 0 then 1 else 0 end) AS count_notexceeded,
        count(*) AS count_all,
        SUM(tperiod) / 60.00 AS th_all,
        SUM(case when auth_exceeded = 0 then tperiod else 0 end) / 60.00 AS th_notexceeded,
        SUM(case when auth_exceeded = 0 then contratrate else 0 end) AS tr_notexceeded,
        SUM(contratrate) AS tr_all
    FROM Approvals 
    JOIN Visit  
      on Visit.appro_id = Approvals.appro_id
    where deleted = 0
    and Visit.c_secondary = 0
    group by userr_id, app_units, c_units, usedunits

)
SELECT DISTINCT userr_id 
FROM t
WHERE ((c_units = 0 and (count_all <> usedunits or count_notexceeded > app_units))
OR  (c_units = 2 and (th_all <> usedunits or th_notexceeded > app_units))
OR  (c_units = 3 and (tr_all <> usedunits or tr_notexceeded > app_units)))
...