【问题标题】:Optimize complicated SQL Update优化复杂的 SQL 更新
【发布时间】:2013-11-20 20:31:59
【问题描述】:

几年前有人在工作中进行了此更新并且它有效,问题是在一个进程中多次调用它需要将近 5 个小时,这不是常规更新,表之间没有一对一的记录匹配,这会根据同一张表中的 parituclar 字段的累积 (SUM) 进行更新,并且事情会变得更加复杂,因为此 SUM 仅限于基于日期和另一个字段的特殊条件。

我认为这类似于(隐式)内部联接,没有 1 对 1 匹配,例如 ALL VS ALL,因此当表中有 7000 条记录时,这件事将处理 7000 * 7000 条记录,超过 5500 万,在我看来应该在这里使用游标,但现在我需要更快的速度,我认为游标不会让我到达那里。

我的问题是:有没有办法重写它并使其更快?注意那个 SUM 的条件,这不是一个容易看到的 UPDATE(至少对我来说)。

更多信息: CodCtaCorriente 和 CodCtaCorrienteMon 是该表上的主键,但正如我之前所说,这里无意进行 1 对 1 匹配,这就是为什么在查询中不使用此键的原因,CodCtaCorrienteMon 用于条件但不用作连接条件(在)。

UPDATE #POS SET SaldoDespuesEvento = 
     (SELECT SUM(Importe) 
        FROM #POS CTACTE2
       WHERE CTACTE2.CodComitente = #POS.CodComitente  
         AND CTACTE2.CodMoneda = #POS.CodMoneda
         AND CTACTE2.EstaAnulado = 0 
         AND (DATEDIFF(day, CTACTE2.FechaLiquidacion, #POS.FechaLiquidacion) > 0 
              OR
             (DATEDIFF(day, CTACTE2.FechaLiquidacion, #POS.FechaLiquidacion) = 0 
              AND (#POS.CodCtaCorrienteMon >= CTACTE2.CodCtaCorrienteMon)))) 
WHERE #POS.EstaAnulado = 0 AND #POS.EsSaldoAnterior = 0

【问题讨论】:

  • 您介意提供一张查询计划的图片吗?
  • 表中有哪些索引?如果您也添加CREATE TABLE 语句会有所帮助。
  • 我尝试为不同的字段添加索引,但变化不大,有趣的是:在 WHERE:#POS .CodCtaCorrienteMon >= CTACTE2.CodCtaCorrienteMon。 CodCtaCorrienteMon 是一个数字和增量字段,当内部查询中的记录在此字段中的值高于外部查询中记录中的相应字段时,此条件的原因是不影响记录(不要 SUM)。
  • 表格总共有多少行,更新了多少行(即通过测试#POS.EstaAnulado = 0 AND #POS.EsSaldoAnterior = 0)?
  • 我在上次测试中使用了 7000 条记录(并不总是相同),这取决于临时表的创建方式。几乎所有行都已更新,该过滤器不会排除许多行。你知道,我开始认为没有更好的方法可以做到这一点。

标签: sql sql-server sql-server-2005 group-by sql-server-2000


【解决方案1】:

从您的查询计划看来,它大部分时间都在索引假脱机之后的过滤器中花费。

如果您要多次运行此查询,我将在“CodComitente”、“CodMoneda”、“EstaAnulado”、“FechaLiquidacion”和“CodCtaCorrienteMon”列上创建一个索引。

我对 Index Spool 迭代器了解不多;但基本上据我了解,它用作在查询时创建的“临时”索引。因此,如果您多次运行此查询,我将创建一次该索引,然后根据需要多次运行查询。

另外,我会尝试创建一个变量来存储求和运算的结果,这样您就可以尽可能避免运行它。

DECLARE @sumVal AS INT

SET @sumVal = SELECT SUM(Importe) 
        FROM #POS CTACTE2
       WHERE CTACTE2.CodComitente = #POS.CodComitente  
         AND CTACTE2.CodMoneda = #POS.CodMoneda
         AND CTACTE2.EstaAnulado = 0 
         AND (DATEDIFF(day, CTACTE2.FechaLiquidacion, #POS.FechaLiquidacion) > 0 
              OR
             (DATEDIFF(day, CTACTE2.FechaLiquidacion, #POS.FechaLiquidacion) = 0 
              AND (#POS.CodCtaCorrienteMon >= CTACTE2.CodCtaCorrienteMon)))

UPDATE #POS SET SaldoDespuesEvento = @sumVal
WHERE #POS.EstaAnulado = 0 AND #POS.EsSaldoAnterior = 0

【讨论】:

  • 这是一个条件和,意味着它不仅是 1 个 SUM,SUM() 与表中的行一样多,请参阅内部查询中的日期条件和 CodCtaCorrienteMon 条件,它们取决于外部查询记录上的值。这不是一个常见的更新,它就像游标内的代码一样,逐条记录地应用 SUM()。
【解决方案2】:

如果没有查询计划,很难有多大帮助,但我会假设,如果 FechaLiquidacion 和 CodCtaCorrienteMon 列上还没有索引,那么只要数据库存储空间不是问题。

【讨论】:

    【解决方案3】:

    找到解决方案,这是一个常见问题:Running Totals

    这是 CURSORS 表现更好的少数情况之一,在这里查看这个和更多可用的解决方案(或浏览 stackoverflow,有很多这样的情况):

    http://weblogs.sqlteam.com/mladenp/archive/2009/07/28/SQL-Server-2005-Fast-Running-Totals.aspx

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-24
      • 2011-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多