如何使光标更快

时间:2012-06-15 17:47:55

标签: sql tsql sql-server-2005 cursor duplicate-removal

我已将此光标写入佣金报告。会发生什么是佣金来自一个表,记录是另一个表。我根据某些标准匹配两个(没有完全匹配)。问题是存在记录的重复。当我将佣金与records表匹配时,可能会导致重复这些重复。因此,代表获得更多报酬。另一方面,委托表中也有重复,但这些都是有效的,因为它们简单意味着账户已经支付了2个月。

我写了这个查询,但运行需要5分钟。我在记录表中有50,000条记录,在佣金表中有100,000条记录。有什么方法可以改进这个光标吗?

/* just preparation of cursor, this is not time consuming */
CREATE TABLE #result
  (
     repid         INT,
     AccountNo     VARCHAR(100),
     supplier      VARCHAR(15),
     CompanyName   VARCHAR(200),
     StartDate     DATETIME,
     EndDate       DATETIME,
     Product       VARCHAR(25),
     commodity     VARCHAR(25),
     ContractEnd   DATETIME,
     EstUsage      INT,
     EnrollStatus  VARCHAR(10),
     EnrollDate    DATETIME,
     ActualEndDate DATETIME,
     MeterStart    DATETIME,
     MeterEnd      DATETIME,
     ActualUsage   INT
  )

DECLARE @AccountNo VARCHAR(100)
DECLARE @supplier VARCHAR(10)
DECLARE @commodity VARCHAR(15)
DECLARE @meterstart DATETIME
DECLARE @meterEnd DATETIME
DECLARE @volume FLOAT
DECLARE @RepID INT
DECLARE @Month INT
DECLARE @Year INT

SET @repID = 80
SET @Month = 1
SET @year = 2012

/* the actual cursor */
DECLARE commission_cursor CURSOR FOR
  SELECT AccountNo,
         supplier,
         commodity,
         meterStart,
         MeterEnd,
         Volume
  FROM   commission
  WHERE  Datepart(m, PaymentDate) = @Month
         AND Datepart(YYYY, PaymentDate) = @Year

OPEN commission_cursor

FETCH next FROM commission_cursor INTO @AccountNo, @supplier, @commodity, @MeterStart, @MeterEnd, @Volume;

WHILE @@fetch_status = 0
  BEGIN
      IF EXISTS (SELECT id
                 FROM   Records
                 WHERE  AccountNo = @AccountNo
                        AND supplier = @supplier
                        AND Commodity = @commodity
                        AND RepID = @repID)
        INSERT INTO #result
        SELECT TOP 1 RepID,
                     AccountNo,
                     Supplier,
                     CompanyName,
                     [Supplier Start Date],
                     [Supplier End Date],
                     Product,
                     Commodity,
                     [customer end date],
                     [Expected Usage],
                     EnrollStatus,
                     ActualStartDate,
                     ActualEndDate,
                     @meterstart,
                     @MeterEnd,
                     @volume
        FROM   Records
        WHERE  AccountNo = @AccountNo
               AND supplier = @supplier
               AND Commodity = @commodity
               AND RepID = @repID
               AND @MeterStart >= Dateadd(dd, -7, ActualStartDate)
               AND @meterEnd <= Isnull(Dateadd(dd, 30, ActualEndDate), '2015-12-31')

      FETCH next FROM commission_cursor INTO @AccountNo, @supplier, @commodity, @MeterStart, @MeterEnd, @Volume;
  END

SELECT *
FROM   #result

/* clean up */
CLOSE commission_cursor

DEALLOCATE commission_cursor

DROP TABLE #result 

我已阅读How to make a T-SQL Cursor faster?的答案,因为我得到的是以表格形式重写此查询。但我确实有另一个使用连接的查询,并且快速闪电。问题是,它无法区分我records表中的重复项。

我能做些什么才能做得更快。这是首要问题。如果没有,你有没有其他办法可以做到。

我特别需要帮助

  • 将使用视图或存储过程帮助
  • 我有一种方法可以在Cursor中使用缓存来加快速度
  • 语法
  • 中的任何其他选项

2 个答案:

答案 0 :(得分:8)

第一个选项是为光标设置资源最少的选项:

declare commission_cursor cursor
local static read_only forward_only
for 

接下来是调查是否需要光标。在这种情况下,我认为你可以通过一次传递和没有循环来做同样的事情:

;WITH x AS 
(
  SELECT 
    rn = ROW_NUMBER() OVER (PARTITION BY r.AccountNo, r.Supplier, r.Commodity, r.RepID 
      ORDER BY r.ActualEndDate DESC),
    r.RepID, 
    r.AccountNo, 
    r.Supplier, 
    r.CompanyName, 
    StartDate = r.[Supplier Start Date], 
    EndDate = r.[Supplier End Date],
    r.Product, 
    r.Commodity, 
    ContractEnd = r.[customer end date], 
    EstUsage = r.[Expected Usage], 
    r.EnrollStatus, 
    EnrollDate = r.ActualStartDate,
    r.ActualEndDate, 
    c.MeterStart, 
    c.MeterEnd, 
    ActualUsage = c.Volume 
  FROM dbo.commission AS c 
  INNER JOIN dbo.Records AS r
    ON c.AccountNo = r.AccountNo
    AND c.Supplier = r.Supplier
    AND c.Commodity = r.Commodity
    AND c.RepID = r.RepID
  WHERE 
    c.PaymentDate >= DATEADD(MONTH, @Month-1, CONVERT(CHAR(4), @Year) + '0101')
    AND c.PaymentDate < DATEADD(MONTH, 1, CONVERT(CHAR(4), @Year) + '0101')
    AND r.RepID = @RepID
)
SELECT RepID, AccountNo, Supplier, CompanyName, StartDate, EndDate, 
  Product, Commodity, ContractEnd, EstUsage, EnrollStatus, EnrollDate, 
  ActualEndDate, MeterStart, MeterEnd, ActualUsage 
FROM x 
WHERE rn = 1 --ORDER BY something;

如果这仍然很慢,那么游标可能不是问题 - 下一步将调查可能实现哪些索引以使此查询更有效。

答案 1 :(得分:0)

临时表是你的朋友

我解决问题的方法,合并来自两个表的数据,以复杂的方式删除重复项,一切都非常快,就是使用临时表。这就是我所做的

创建一个#temp表,从两个表中获取合并数据。即使您不需要,也要确保在两个表中都包含ID字段。这有助于删除重复项。

现在您可以对此表进行所有类型的计算。从表B中删除重复项,只需删除重复的表B ID。从表A中删除重复项,只需删除重复的表A ID。这个问题有更复杂的问题,但至少这可能是解决问题的最佳方法,如果游标过于昂贵并且需要相当长的时间来计算,那么它可以大大加快。在我的情况下,它需要+5分钟。 #temp表查询大约5秒,其中有更多的计算。


在应用Aaron解决方案时,光标没有变得更快。第二个查询更快但它没有给我正确的答案,所以最后我使用临时表。这是我自己的答案。