这个SQL语句可以优化吗?

时间:2012-04-26 16:59:34

标签: sql sql-optimization

这似乎需要花费很长时间才能处理大型数据集。将第一个和最后三个查询合并为1会使它更快吗?有没有人对什么可能使它更快有任何意见?我很感激。

update "detail" set bal = (units * amount) where code_type = 'AB'; 
update "detail" set bal = (units * amount) where code_type = 'CD';
update "detail" set bal = (units * amount) where code_type = 'EF';
update "detail" set bal = (units * amount * -1) where code_type = 'GH';
update "detail" set bal = (units * amount * -1) where code_type = 'IK';
update "detail" set bal = (units * amount * -1) where code_type = 'LM';
update "detail" set bal = 0 where code_type = 'NO';

另外 -

update bill set pay = 
  (select round(sum(bd1.bal),2) from "detail" bd1 where 
  bd1.inv = bill.inv and 
  (bd1.code_type = 'AB' or bd1.code_type = 'CD')); 
update bill set pay = 0 where pay is null;
update bill set cost = 
  (select round(sum(bd2.bal),2) from "detail" bd2 where 
  bd2.inv = bill.inv and 
  (not (bd2.code_type = 'AB' or bd2.code_type = 'CD'))); 
update bill set cost = 0 where cost is null;
update bill set balance = round(cost + pay,2);

由于

4 个答案:

答案 0 :(得分:3)

性能可能很糟糕,因为您正在更新整个表,并且您要更新它十二次。如果表格非常大,那就可以了 时间。此外,这两个嵌入式子查询将每行运行一次。哎哟。

以下frankenquery将所有内容都汇总到一个语句中。它仍然必须击中整个表,但至少它只执行一次。我无法检查语法 或者根据数据对其进行测试,但是这个或类似它的东西应该可以工作。

EDITED,将其拆分为两个更新(因此,需要两个表扫描)

UPDATE Detail
 set
   bal = case
           when code_type in ('AB','CD','EF') then  bi.units * bi.amount
           when code_type in ('gh','ik','lm') then -bi.units * bi.amount
           when code_type = 'NO' then 0
           else bal  --  If none of the above, change nothing
         end

UPDATE Bill
 set
   payments = isnull(bd1.amount, payments)  --  This changes nothing if nothing was calculated
  ,pay = case
           when pay is null then 0
           else pay
         end
   --  Ok, problem with cost: what if calculated amount is 0 and current value is non-zero?
   --  I've insufficient data here to correctly resolve all the possible permutations
  ,cost = case
            when bd2.amount is not null then cost
            when cost is null then 0
            else cost
          end
  ,balance = round(charges + isnull(bd1.amount, bi.payments), 2)
 from Bill bi
  --  These subqueries could be combined into one using similar CASE logic,
  --  and it would probably perform better (one table scan vs. two).  I'm
  --  leaving it this way for demonstration, and to keep the overall query
  --  a bit simpler.
  left outer join (select
                      inv
                     ,round(sum(bd1.bal), 2) amount
                    from detail
                    where code_type = 'AB'
                     or code_type = 'CD'
                    group by inv) bd1
   on bd1.inv = bi.inv  --  ADDED in second edit
  left outer join (select 
                      inv  --  RENAMED in second edit
                     ,round(sum(bd2.bal), 2) amount
                    from detail
                    where code_type <> 'AB'
                     and code_type <> 'CD'
                    group by inv) bd2  --  RENAMED in second edit
   on bd2.invoive = bi.inv  --  ADDED in second edit

道德:CASE语句可以是SQL开发人员最好的朋友。

答案 1 :(得分:1)

第一个查询可以这样写:

UPDATE "detail"
SET    bal =
       CASE
              WHEN code_type = 'NO'
              THEN 0
              ELSE
                     CASE
                            WHEN code_type IN ('AB',
                                               'CD',
                                               'EF')THEN 1
                            ELSE -1
                     END
       END        * (units * amount)
WHERE  code_type IN ('AB','CD','EF','GH','IK','KL','NO');

拥有code_type索引会快速过滤掉您需要更新的符合条件的行。根据表结构的其余部分,您的速度可能会有所不同,但我认为这将是最快的版本。因为这里唯一重要的是基于代码类型的0,1或-1,这是你检查的,然后乘以单位*金额。

更新: 您的第二批更新也可以一批编写:

UPDATE b
SET    payments = COALESCE(x.bal,0),
       cost     = COALESCE(y.bal,0),
       balance  = ROUND(charges + COALESCE(x.bal,0),2)
FROM   bill b
       LEFT OUTER JOIN
              ( SELECT  ROUND(SUM(bd1.bal),2) AS bal,
                       inv
              FROM     "detail" bd1
              WHERE    bd1.code_type IN ('AB',
                                         'CD')
              GROUP BY bd1.inv
              )
              x
       ON     x.inv = b.inv
       LEFT OUTER JOIN
              ( SELECT  ROUND(SUM(bd2.bal),2) AS bal,
                       invoice
              FROM     "detail" bd2
              WHERE    bd2.code_type NOT IN ('AB',
                                             'CD')
              GROUP BY bd2.invoice
              )
              y
       ON     y.invoice = b.invoice

提高速度的提示:

  • 详细信息表索引,code_type和inv列,在索引中包含bal
  • 详细信息表索引,code_type和invoice列,包括索引中的bal。
  • 对bill表,inv和invoice的索引。

答案 2 :(得分:0)

我认为前3个陈述你可以在单个陈述中这样做:

update detail set bal = (units * amount) where code_type in( 'AB','CD' )

您也可以为接下来的3个陈述做同样的事。

答案 3 :(得分:0)

目前,您正在为每个查询重新计算units * amountunits * amount * -1。计算units * amount一次并结合前几个查询可以提高性能,但我不知道多少:

declare @total int
set @total = units * amount
update "detail" set bal = @total where code_type in ('AB', 'CD', 'EF');  
update "detail" set bal = (@total * -1) where code_type in ('GH', 'IK','LM');  
update "detail" set bal = 0 where code_type = 'NO';