提高INSERT INTO SELECT查询性能?

时间:2019-10-10 19:47:38

标签: sql sql-server performance tsql

我正在尝试改进旧的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 个答案:

答案 0 :(得分:3)

很棒的工作,消除了循环!

我将指出一个可能的性能问题,特别是year(date_month)=year(@tdate)

因为列包装在函数中,所以它是non-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是否给出了"Missing Index" recommendation(如果它确实建议了索引,它应该出现在计划图的正上方)。尝试添加建议的丢失索引,然后检查是否获得性能改进。如果没有改善,请删除索引-有时建议无济于事。

已更新

使用带有不同swercharge条件的pay_bill表的3个子查询(填充列billtx_billWHERE)将导致至少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在his answer中对您的previous question所说的那样,正确设置代码格式可显着减少理解和更改代码所需的时间。我要进一步说,代码格式代表您对当前问题的态度和理解。

答案 1 :(得分:1)

我添加了非聚集索引,然后我的查询需要5秒钟才能运行,这几乎解决了我的问题。 @Alex感谢您的辛勤工作和时间。我会检查您的提示。我对您的评论进行投票