Улучшить производительность INSERT INTO SELECT? - PullRequest
1 голос
/ 10 октября 2019

Я пытаюсь улучшить старый запрос while. Пока пробовал это, но все еще медленно. Не уверен, что SUM может выполняться медленно.

Старый запрос (10 минут +)

ALTER PROCEDURE [dbo].[process_tax]
   @userid VARCHAR(10),
   @remark NVARCHAR(500),
   @tdate DATE,
   @roadno NVARCHAR(10),
   @inst INT
AS
BEGIN 
    IF OBJECT_ID('tempdb..#tempProcess_tax_1') IS NOT NULL  
        DROP TABLE #tempProcess_tax_1

    CREATE TABLE #tempProcess_tax_1 
    (
         RowID INT IDENTITY(1, 1), 
         clid_ INT,
         hlid_ INT,
         holdinNo_ NVARCHAR(500),
         holding_ NVARCHAR(50),
         clientid_ NVARCHAR(500),
         clientName_ NVARCHAR(500)
    )

    INSERT INTO #tempProcess_tax_1 (clid_, hlid_, holdinNo_, holding_, clientid_, clientName_)
        SELECT
            cl.clid AS clid_, cl.id AS hlid_, holdinNo, holding,
            ClientID AS clientid_, ClientName AS clientName_  
        FROM
            tx_holding AS cl 
        WHERE
            cl.status = 1 AND cl.roadno = @roadno
            AND cl.id IN (SELECT hlid FROM tx_asset WHERE asset IS NOT NULL) 
            AND cl.clid IN (SELECT id FROM tbl_client WHERE client_type = 'Non-Govt.') 
            AND cl.id NOT IN (SELECT hlid FROM tx_bill_pay 
                              WHERE YEAR(date_month) = YEAR(@tdate) 
                                 AND hlid IS NOT NULL 
                              GROUP BY hlid)

    DECLARE @NumberRecords_1 INT, @RowCounter_1 INT

    SET @NumberRecords_1 = (SELECT COUNT(*) FROM #tempProcess_tax_1)
    SET @RowCounter_1 = 1

    WHILE @RowCounter_1 <= @NumberRecords_1
    BEGIN
        DECLARE @clid_ INT
        DECLARE @hlid_ INT
        DECLARE @holdinNo_ NVARCHAR(50)
        DECLARE @holding_ NVARCHAR(50)
        DECLARE @clientid_ NVARCHAR(100)
        DECLARE @clientName_ NVARCHAR(250)
        DECLARE @bill AS MONEY
        DECLARE @sr AS MONEY;

   SELECT  
       @clid_ = clid_,
       @hlid_ = hlid_,
       @holdinNo_ = holdinNo_, @holding_ = holding_,
       @clientid_ = clientid_, @clientName_ = clientName_
   FROM 
       #tempProcess_tax_1 
   WHERE 
       RowID = @RowCounter_1

SET @bill = (SELECT 
                 CASE WHEN SUM(netvalue) IS NULL
                         THEN 0 
                         ELSE SUM(netvalue) 
                 END  
             FROM 
                 tx_bill 
             WHERE 
                 hlid = @hlid_ 
                 AND itemID NOT IN (8, 6) 
                 AND YEAR(date_month) = YEAR(@tdate))
    SET @sr = (SELECT
                   CASE WHEN SUM(asset * rate / 100) IS NULL
                           THEN 0 
                           ELSE SUM(asset * rate / 100) 
                   END
               FROM 
                   tx_bill 
               WHERE
                   hlid = @hlid_ 
                   AND itemID = 6 
                   AND YEAR(date_month) = YEAR(@tdate))

    INSERT INTO tx_bill_pay(clid, hlid, swercharge, pay_bill, pdate, bill_id, holdingNo, holding, ClientID, ClientName, billno, date_month, bill, install, inserted_by, inserted_date) 
    VALUES (@clid_, @hlid_, @sr, @bill / 4, DATEADD(day, -1, DATEADD(m, 3, @tdate)), CONCAT(@holdinNo_, YEAR(@tdate), '1'), @holdinNo_, @holding_, @clientid_, @clientName_, CONCAT(@holdinNo_, YEAR@tdate)), @tdate, @bill, 1, @userid, GETDATE())

    INSERT INTO tx_bill_pay(clid, hlid, swercharge, pay_bill, pdate, bill_id, holdingNo, holding, ClientID, ClientName, billno, date_month, bill, install, inserted_by, inserted_date) 
    VALUES (@clid_, @hlid_, 0, 2 * (@bill / 4), DATEADD(day, -1, DATEADD(m, 6, @tdate)), CONCAT(@holdinNo_, YEAR(@tdate), '2'), @holdinNo_, @holding_, @clientid_, @clientName_, CONCAT(@holdinNo_, YEAR(@tdate)), @tdate, @bill, 2, @userid, GETDATE())

    SET @RowCounter_1 = @RowCounter_1 + 1
END

DROP TABLE #tempProcess_tax_1
END

Новый запрос (1-2 минуты)

ALTER PROCEDURE [dbo].[process_tax]
   @userid varchar(10),
   @remark nvarchar(500),
   @tdate date ,
   @roadno nvarchar(10),
   @inst int
   as
BEGIN 

 insert  into tx_bill_pay(
          clid,
          hlid,
          swercharge,
          pay_bill,
          pdate,
          bill_id,
          holdingNo,
          holding,
          ClientID, 
          ClientName,
          billno, 
          date_month, 
          bill,
          install ,
          inserted_by, 
          inserted_date)

     select 
           cl.clid,
           cl.id,
     swercharge=(select  case when sum(asset*rate/100) is null then 0 else 
               sum(asset*rate/100) end  from tx_bill where hlid=cl.id and 
               itemID =6 and year(date_month)=YEAR(@tdate)),
     pay_bill=(select  case when sum(netvalue) is null then 0 else 
             sum(netvalue) end  from tx_bill where hlid=cl.id and itemID not 
             in(8,6) and year(date_month)=YEAR(@tdate))/4,  
    DATEADD(day,-1,
    DATEADD(m,3,@tdate)),
    CONCAT(cl.holdinNo, year(@tdate),'1'),
    cl.holdinNo,
    cl.holding,
    cl.ClientID, 
    cl.clientName, 
    CONCAT(cl.holdinNo, 
    year(@tdate)), 
    @tdate,
    bill=(select  case when sum(netvalue) is null then 0 else sum(netvalue) 
         end  from tx_bill where hlid=cl.id and itemID not in(8,6) and 
         year(date_month)=YEAR(@tdate))/4,
    1, 
    @userid, getdate()  
    from  
    (select * 
    from tx_holding as cl 
    where cl.status=1 and cl.roadno=@roadno) AS cl
    INNER JOIN (
     select DISTINCT hlid from tx_asset where asset is not null
     ) AS A 
    ON Cl.id = A.hlid 
     INNER JOIN (
      select DISTINCT  id from tbl_client where client_type='Non-Govt.'
     ) AS C 
    ON   cl.clid=C.id
    WHERE    NOT EXISTS
        (   SELECT  1
            FROM    tx_bill_pay as bp
            WHERE    year(date_month)=year(@tdate)
            and bp.hlid=cl.id
        )   


 insert  into tx_bill_pay(clid,hlid 
    ,swercharge,pay_bill,pdate,bill_id,holdingNo,holding,ClientID, 
    ClientName, billno, date_month, bill, install ,inserted_by, 
    inserted_date)
select 
    cl.clid,
    cl.id,
    0,
    pay_bill=2*((select  case when sum(netvalue) is null then 0 else sum(netvalue) end  from tx_bill where hlid=cl.id and itemID not in(8,6) and year(date_month)=YEAR(@tdate))/4),
    DATEADD(day,-1,
    DATEADD(m,3,@tdate)),
    CONCAT(cl.holdinNo, year(@tdate),'2'),
    cl.holdinNo,
    cl.holding,
    cl.ClientID, 
    cl.clientName, 
    CONCAT(cl.holdinNo, year(@tdate)) , 
    @tdate,
    bill=(select  case when sum(netvalue) is null then 0 else sum(netvalue) 
         end  from tx_bill where hlid=cl.id and itemID not in(8,6) and year(date_month)=YEAR(@tdate))/4,
    2, 
    @userid, getdate()  
from  
    (select * 
    from tx_holding as cl 
    where cl.status=1 and cl.roadno=@roadno) AS cl
    INNER JOIN (
     select DISTINCT hlid from tx_asset where asset is not null
     ) AS A 
    ON Cl.id = A.hlid 
     INNER JOIN (
      select DISTINCT  id from tbl_client where client_type='Non-Govt.'
     ) AS C 
    ON   cl.clid=C.id
    WHERE    cl.id not in
        (   SELECT  hlid
            FROM    tx_bill_pay
            WHERE  year(date_month)=year(@tdate)
            and hlid is not null group by hlid
        )

Ответы [ 2 ]

2 голосов
/ 10 октября 2019

Отличная работа по удалению цикла!

Я укажу на одну возможную проблему с производительностью, в частности year(date_month)=year(@tdate).

Поскольку столбец заключен в функцию, он не-SARGABLE . Это означает, что date_month значения не могут быть оценены напрямую, и, следовательно, индексы и статистика для этого столбца не могут быть использованы.

Для решения этой проблемы я предлагаю следующие изменения:

В верхней частиSP определяет еще две переменные:

DECLARE @YearStart AS DATETIME, @NextYearStart DATETIME
SET @YearStart = DATEADD(yy, DATEDIFF(yy, 0, @tdate ), 0 )
SET @NextYearStart = DATEADD( yy, @YearStart, 1 )

Затем замените все экземпляры year(date_month)=year(@tdate) на

@YearStart <= date_month AND date_month < @NextYearStart

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

Далее я бы посмотрел план запроса и выяснил, нет ли в SQL Server «Отсутствует». Индекс "рекомендация (он должен появляться чуть выше план-диаграммы, если он действительно предлагает индекс). Попробуйте добавить рекомендуемые отсутствующие индексы, а затем проверьте, не достигнуто ли повышение производительности. Если вы не получили улучшения, удалите индекс - иногда предложения не помогают.

Обновлено

Имеет 3 подзапроса (заполнение столбцов swercharge, pay_bill, bill) использование таблицы tx_bill с различными условиями WHERE приведет к не менее чем 3 оценкам этой таблицы за одно выполнение основного запроса.

В зависимости от размера таблицы может быть более эффективнымрассчитайте все 3 подзапроса один раз и сохраните результаты во временную таблицу (или переменную таблицы) следующим образом:

SELECT hlid,
    ISNULL( SUM( CASE WHEN itemID NOT IN (8, 6) THEN netvalue END ), 0 ) AS Bill,
    ISNULL( SUM( CASE WHEN itemID = 6 THEN asset * rate / 100 END ), 0 ) AS Sr
INTO #Bills
FROM tx_bill 
WHERE @YearStart <= date_month AND date_month < @NextYearStart
    AND NOT hlid IS NULL
GROUP BY hlid

В основных запросах присоединитесь к этой таблице следующим образом:

LEFT JOIN #SumBills AS SumBills ON SumBills.hlid = cl.hlid

и в SELECT измените назначения, как показано в примере ниже:

pay_bill= ISNULL( SumBills.Bill, 0 ) / 4,

Примечание: Я полагаю, у вас есть ошибка в столбце bill, так как значение делится на4, где его нет в исходном курсоре.

Последний пункт:

Как сказал @GarethD в свой ответ на ваш предыдущий вопрос правильное форматирование кода значительно сокращает время, необходимое для понимания иизменить кодЯ бы сделал еще один шаг вперед и сказал бы, что формат кода отражает ваше отношение к проблеме и ее понимание.

0 голосов
/ 13 октября 2019

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

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