Как улучшить производительность при вставке цикла в SQL Server? - PullRequest
1 голос
/ 08 октября 2019

Вот мой SQL-запрос. Это вставка почти 6500+ строк из временной таблицы. Но это занимает 15 минут! ,Как я могу улучшить это? Спасибо

ALTER proc [dbo].[Process_bill]
@userid varchar(10),
@remark nvarchar(500),
@tdate date ,
@pdate date
as

BEGIN 


IF OBJECT_ID('tempdb.dbo..#temptbl_bill', 'U') IS NOT NULL
    DROP TABLE #temptbl_bill; 

CREATE TABLE #temptbl_bill (
   RowID int IDENTITY(1, 1), 
   ------------

)

// instert into temp table

DECLARE @NumberRecords int, @RowCounter int
DECLARE @batch INT
SET @batch   = 300
SET @NumberRecords = (SELECT COUNT(*) FROM #temptbl_bill)
SET @RowCounter = 1

SET NOCOUNT ON  

BEGIN TRANSACTION

WHILE @RowCounter <= @NumberRecords
BEGIN

declare @clid int
declare @hlid int
declare @holdinNo nvarchar(150)
declare @clientid nvarchar(100)
declare @clientName nvarchar(50)
declare @floor int
declare @radius nvarchar(50)
declare @bill money 
declare @others money
declare @frate int
declare @due money
DECLARE @fine money
DECLARE @rebate money

IF @RowCounter > 0 AND ((@RowCounter % @batch = 0) OR (@RowCounter = @NumberRecords))           
BEGIN                   
    COMMIT TRANSACTION
    PRINT CONCAT('Transaction #', CEILING(@RowCounter/ CAST(@batch AS FLOAT)), ' committed (', @RowCounter,' rows)');
    BEGIN TRANSACTION
END;

 // multiple select 


// insert to destination  table

Print 'RowCount -' +cast(@RowCounter as varchar(20))  + 'batch -' + cast(@batch as varchar(20))

SET @RowCounter = @RowCounter + 1;

END

COMMIT TRANSACTION

PRINT CONCAT('Transaction #', CEILING(@RowCounter/ CAST(@batch AS FLOAT)), ' committed (',
@RowCounter,' rows)');

SET NOCOUNT OFF 

DROP TABLE #temptbl_bill

END

GO

1 Ответ

5 голосов
/ 08 октября 2019

Как было сказано в комментариях, цикл совершенно не нужен. Способ улучшить производительность любого цикла - полностью удалить его. Циклы являются последним средством в SQL.

Насколько я могу судить, ваша вставка может быть записана одним оператором:

INSERT tbl_bill(clid, hlid, holdingNo,ClientID, ClientName, billno, date_month, unit, others, fine, due, bill, rebate, remark, payment_date, inserted_by, inserted_date) 
SELECT  clid = c.id, 
        hlid = h.id,
        h.holdinNo ,
        c.cliendID, 
        clientName = CAST(c.clientName AS NVARCHAR(50)), 
        BillNo = CONCAT(h.holdinNo, MONTH(@tdate), YEAR(@tdate)),
        date_month = @tDate,
        unit = 0,
        others = CASE WHEN h.hfloor = 0 THEN rs.frate * (h.hfloor - 1) ELSE 0 END, 
        fine = bs.FineRate * b.Due / 100, 
        due  = b.Due, 
        bill = @bill,   -- This is declared but never assigned
        rebate = bs.rebate,
        remark = @remark,
        payment_date = @pdate,
        inserted_by = @userid, 
        inserted_date = GETDATE()
FROM    (   SELECT  id, clientdID, ClientName
            FROM    tbl_client 
            WHERE   status = 1
        ) AS c
        INNER JOIN 
        (   SELECT  id, holdinNo, [floor], connect_radius
            FROM    tx_holding 
            WHERE   status = 1 
            AND     connect_radius <> '0' 
            AND     type = 'Residential'
        ) AS h
            ON c.id = h.clid
        LEFT JOIN tbl_radius_setting AS rs
            ON rs.radius= CONVERT(real,h.connect_radius) 
            AND rs.status = 1 
            AND rs.type = 'Non-Govt.'
        LEFT JOIN tbl_bill_setting AS bs
            ON bs.Status = 1
        LEFT JOIN 
        (   SELECT  hlid,
                    SUM(netbill) AS Due
            FROM    tbl_bill AS b
            WHERE   date_month < @tdate  
            AND     (b.ispay = 0 OR b.ispay IS NULL) 
            GROUP BY hlid
        ) AS b
            ON b.hlid = h.id
WHERE   NOT EXISTS 
        (   SELECT  1 
            FROM    tbl_bill AS tb 
            WHERE   EOMONTH(@tdate) = EOMONTH(date_month)       
            AND     tb.holdingNo = h.holdinNo
            AND     (tb.update_by IS NOT NULL OR tb.ispay=1)
        );

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

Помимо адаптации этого к работе в качестве единого утверждения, я сделал ряд модификаций. к существующему коду:

  • Поменяйте местами NOT IN на NOT EXISTS, чтобы избежать проблем с нулевыми записями. Если holdingNo обнуляем, они эквивалентны, если holdingNo обнуляем, NOT EXISTS безопаснее - Не существует против НЕ IN
  • Используемый вами синтаксис объединения был заменен 27лет назад, поэтому я перешел с синтаксиса соединения ANSI-89 на ANSI-92. - Вредные привычки к удару: использование ДЖОЙНОВ в старом стиле
  • Изменены предикаты YEAR(date_month) = YEAR(@tDate) AND MONTH(date_month) = MONTH(@tDate), чтобы они стали EOMONTH(@tdate) = EOMONTH(date_month). Синтаксически они одинаковы, но EOMONTH - это Sargable , тогда как MONTH и YEAR - нет.

Затем несколько дополнительных ссылок / предложений, которые непосредственно связаны с изменениямиЯ сделал

  • Хотя я убрал while lopp, не попадайтесь в ловушку, думая, что это лучше, чем курсор. Правильно объявленный курсор будет выполнять цикл while, как ваш - Плохие привычки к удару: Думать, что цикл WHILE не является курсором
  • Общий консенсус заключается в том, чтопрефикс имен объектов не очень хорошая идея. Из контекста должно быть либо очевидно, является ли объект таблицей / представлением, либо функцией / процедурой, либо это не должно иметь значения, т. Е. Нет необходимости различать таблицу или представление, и на самом деле мы можем захотеть изменитьот одного к другому, поэтому наличие префикса делает вещи хуже, а не лучше.
  • Среднее соотношение времени, потраченного на чтение кода, и времени, потраченного на написание кода, составляет около 10: 1 - поэтому стоит отформатироватьваш код, когда вы пишете его, чтобы его было легко читать. Это очень субъективно для SQL, и я бы не рекомендовал никаких конкретных соглашений, но я не могу поверить, что на секунду вы обнаружите, что ваш исходный код свободно распространяется и легко читается. Мне потребовалось около 10 минут, чтобы распутать первое заявление вставки.

РЕДАКТИРОВАТЬ

Вышеуказанное неверно, EOMONTH() не может быть sargable, поэтому не работает лучше, чем YEAR(x) = YEAR(y) AND MONTH(x) = MONTH(y), хотяэто все еще немного проще. Если вам нужен действительно предикат sargable, вам нужно создать начальную и конечную дату, используя @tdate, поэтому вы можете использовать:

DATEADD(MONTH, DATEDIFF(MONTH, '19000101', @tdate), '19000101') 

, чтобы получить первый день месяца для @tdate, затемпочти тот же форум, но добавьте месяцы к 1 февраля 1900 года, а не к 1 января, чтобы получить начало следующего месяца:

DATEADD(MONTH, DATEDIFF(MONTH, '19000201', @tdate), '19000201') 

Итак, следующее:

DECLARE @Tdate DATE = '2019-10-11';
SELECT  DATEADD(MONTH, DATEDIFF(MONTH, '19000101', @tdate), '19000101'),
        DATEADD(MONTH, DATEDIFF(MONTH, '19000201', @tdate), '19000201');

Вернет 1-еОктябрь и 1 ноября соответственно. Поместив это обратно в исходный запрос, вы получите:

WHERE   NOT EXISTS 
        (   SELECT  1 
            FROM    tbl_bill AS tb 
            WHERE   date_month >= DATEADD(MONTH, DATEDIFF(MONTH, '19000101', @tdate), '19000101'),
            AND     date_month < DATEADD(MONTH, DATEDIFF(MONTH, '19000201', @tdate), '19000201')
            AND     tb.holdingNo = h.holdinNo
            AND     (tb.update_by IS NOT NULL OR tb.ispay=1)
        );
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...