这是我的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
正如评论中所说,循环是完全不必要的。改善任何循环性能的方法是将其完全删除。循环是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
YEAR(date_month) = YEAR(@tDate) AND MONTH(date_month) = MONTH(@tDate)
EOMONTH(@tdate) = EOMONTH(date_month)
MONTH
YEAR
然后是其他一些与我所做的更改直接相关的链接/建议
编辑
上面的内容是不正确的,EOMONTH()不是可固定的,因此YEAR(x) = YEAR(y) AND MONTH(x) = MONTH(y),它的性能仍然比上要简单一些,因此并没有比更好。如果您想要一个真正可预测的谓词,则需要使用来创建开始日期和结束日期@tdate,因此您可以使用:
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) );