小编典典

如何提高SQL Server中的while循环插入性能?

sql

这是我的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

阅读 286

收藏
2021-04-22

共1个答案

小编典典

正如评论中所说,循环是完全不必要的。改善任何循环性能的方法是将其完全删除。循环是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 INNOT EXISTS避免与空记录任何问题。如果holdingNo为null,则等效;如果holdingNo为null,则更NOT EXISTS安全- 不存在与不存在
  • 您使用的联接语法在27年前已被替换,因此我从ANSI-89联接语法切换为ANSI-92。-坏习惯:使用老式的JOIN
  • 的谓词YEAR(date_month) = YEAR(@tDate) AND MONTH(date_month) = MONTH(@tDate)变为EOMONTH(@tdate) = EOMONTH(date_month)。这些在语法上是相同的,但是EOMONTH是Sargable,而MONTHYEAR不是。

然后是其他一些与我所做的更改直接相关的链接/建议

  • 尽管我删除了while记录,但不要陷入以为它比游标更好的陷阱。正确声明的游标将像您一样执行while循环-踢的不良习惯:认为WHILE循环不是CURSOR
  • 普遍的共识是,前缀对象名称是不是一个好主意。从上下文的角度来看,如果对象是表/视图或函数/过程,则它应该是无关紧要的;即,对象或表/视图或函数/过程是不相关的,即,无需区分表或视图,实际上,我们可能希望更改从一个到另一个,所以拥有前缀会使情况变得更糟,而不是更好。
  • 花费在阅读代码上的时间与花费在编写代码上的时间的平均比率约为10:1-因此,值得在编写代码时对其进行格式化,以使其易于阅读。这在SQL中是非常主观的,因此我不建议您使用任何特殊约定,但是我无法相信您会发现您的原始代码可以自由流通并且易于阅读。我花了大约10分钟的时间才弄清了第一条插入语句。

编辑

上面的内容是不正确的,EOMONTH()不是可固定的,因此YEAR(x) = YEAR(y) AND MONTH(x) = MONTH(y),它的性能仍然比上要简单一些,因此并没有比更好。如果您想要一个真正可预测的谓词,则需要使用来创建开始日期和结束日期@tdate,因此您可以使用:

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

来获取的月份的第一天@tdate,然后是几乎相同的论坛,但要在1900年2月1日之前(而不是1月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');

分别将于10月1日和11月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)
        );
2021-04-22