2017-08-29 55 views
5

我有一個包含一些值列,公式和結果列的表。更新表中的每一行

|rownum|value1|value2|value3|formula    |result| 
|------|------|------|------|--------------------|------| 
|1  |11 |30 |8  |value1/value2*value3|  | 
|2  |43 |0  |93 |value1-value2+value3|  | 

我想用公式的結果填充result列。

目前我做了這個查詢:

DECLARE @v_sql NVARCHAR(MAX) 

SET @v_Sql = CAST ((SELECT 
      ' UPDATE [table] ' + 
      ' SET [result] = ' + table.[formula] + 
      ' WHERE [rownum] = ' + CAST(table.[rownum] as nvarchar(255)) + 
      ';' 
      FROM [table] 
      FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)') AS NVARCHAR (MAX)) 

EXEC (@v_Sql) 

的問題是,這需要很長的時間。表格中的#行將會是5-10百萬。

有什麼辦法可以加快速度嗎?對這個問題的替代方法?

非常感謝!

+0

您可能想在[代碼評論](https://codereview.stackexchange.com/)上發佈此信息。這是工作代碼更好的地方,你想加快速度。 –

+0

是公式行爲計算列嗎? –

+0

您需要在'update'中尋找瓶頸。你有任何觸發器,索引或FK?你使用什麼樣的隔離?查看查詢計劃。 –

回答

0

感謝所有的答覆和想法。最後,通過將公式保存在維度而不是事實表格中來解決問題。 這將爲維度中的每一行生成1個更新語句,並將其應用於所有與where子句相關的相關事實行,而不是每個事實行的1個更新語句。 處理時間從1.5小時降到不到1秒。

3

假設操作順序的規則,只有捂住簡單的公式例如:

UPDATE [table] 
SET [result] = case replace(replace(replace([formula],'value1', ''), 'Value2', ''), 'Value3', '') 
     when '++' then [value1] + [value2] + [Value3] 
     when '+-' then [value1] + [value2] - [Value3] 
     when '+*' then [value1] + [value2] * [Value3] 
     when '+/' then [value1] + [value2]/[Value3] 
     when '-+' then [value1] - [value2] + [Value3] 
     when '--' then [value1] - [value2] - [Value3] 
     when '-*' then [value1] - [value2] * [Value3] 
     when '-/' then [value1] - [value2]/[Value3] 
     when '*+' then [value1] * [value2] + [Value3] 
     when '*-' then [value1] * [value2] - [Value3] 
     when '**' then [value1] * [value2] * [Value3] 
     when '*/' then [value1] * [value2]/[Value3] 
     when '/+' then [value1]/[value2] + [Value3] 
     when '/-' then [value1]/[value2] - [Value3] 
     when '/*' then [value1]/[value2] * [Value3] 
     when '//' then [value1]/[value2]/[Value3] 
     end 
from [Table] 
+0

謝謝,但我提出問題的方式非常簡單。實際上有7個領域。該公式由1個或多個這些字段組成,可以用任何方式構建。除了必須在數學上正確之外,對公式的結構沒有任何限制。 – tv87

+1

@ tv87我添加了另一個可能有用的答案。 – cloudsafe

1

兩個簡單的事情浮現在腦海:

  1. 確保有上,如果你是rownum列的索引單獨更新每一行。

  2. 如果只有幾個不同的公式,您可以在一個UPDATE中更新具有相同公式的所有行,而不是單獨更新每一行。在這種情況下,formula列的索引將有所幫助。

+0

嗨,我在rownum列上添加了一個索引,加快了查詢速度。不幸的是,它仍然很慢。 使用的不同公式的數量是有限的,並且取決於與此錶鏈接的維度。因此,對於100萬行,有100個獨特的公式可以應用。 我會嘗試將公式移到此維度表並從那裏檢索它以限制必須執行的更新語句的數量。 – tv87

+0

@ tv87,運行UPDATE 100次的速度肯定快於1,000,000次。特別是當更新行的總數相同時。它應該快近一萬倍。不過,有關「公式」列的索引很重要。 –

1

按公式類型更新批量更快嗎? [公式]上需要的索引還有:

DECLARE @v_sql NVARCHAR(MAX) 

SET @v_Sql = CAST ((SELECT 
      ' UPDATE [table] ' + 
      ' SET [result] = ' + [table].[formula] + 
      ' WHERE [formula] = ''' + [table].[formula] + ''';' 
      FROM [table] 
      group by [table].[formula] 
      FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)') AS NVARCHAR (MAX)) 
exec(@v_Sql) 
0

使用觸發器選項,但現在,塊更新將產生較小的影響。

TOP(5000)每次都會WHERE [result] is null OR [result]=''

GO 20000將執行這個查詢20000次(10萬行),這將繼續爲UPDATE語句執行,直到返回0的記錄只更新5000行。

DECLARE @v_sql NVARCHAR(MAX) 

SET @v_Sql = CAST ((SELECT 
      ' UPDATE TOP (5000) [table] ' + 
      ' SET [result] = ' + [table].[formula] + 
      ' WHERE [formula] = ''' + [table].[formula] + ''' 
      AND ([result] is null OR [result]='');' 
      FROM [table] 
      group by [table].[formula] 
      FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)') AS NVARCHAR (MAX)) 
exec(@v_Sql) 

    GO 20000 

在此之後,創建觸發器

0

我剛剛創建了500萬行的表。隨着表結構爲:

rn t1 t2 t3 formula 
1 80 23 93 t1/t2 * t3 
2 80 87 30 t1/t2 * t3 
3 92 83 63 t1/t2 * t3 
4 68 19 36 t1/t2 * t3 
5 65 63 10 t1/t2 * t3 

如果你確信,所有你的公式是有效的,你會不會具有例如除以零或數據類型的溢出,在這種情況下,你可以使自己的eval()函數在SQL服務器中。

我爲公式中的3個值創建了我自己的函數,其符號如下: '+',' - ','*','/'。

功能碼是:

use db_test; 
go 

alter function dbo.eval(@a varchar(max)) 
returns float 
as 
begin 
    set @a = replace(@a, ' ', ''); 

    declare @pos1 int = PATINDEX('%[+/*-]%', @a); 
    declare @t1 float = cast(substring(@a, 1, @pos1 - 1) as float); 
    declare @sign1 char(1) = substring(@a, @pos1, 1); 
    set @a = substring(@a, @pos1 + 1, len(@a) - @pos1); 

    declare @pos2 int = PATINDEX('%[+/*-]%', @a); 
    declare @t2 float = cast(substring(@a, 1, @pos2 - 1) as float); 
    declare @sign2 char(1) = substring(@a, @pos2, 1); 
    set @a = substring(@a, @pos2 + 1, len(@a) - @pos2); 

    declare @t3 float = cast(@a as float); 

    set @t1 = (
     case @sign1 
      when '+' then @t1 + @t2 
      when '-' then @t1 - @t2 
      when '*' then @t1 * @t2 
      when '/' then @t1/@t2 
     end 
    ); 

    set @t1 = (
     case @sign2 
      when '+' then @t1 + @t3 
      when '-' then @t1 - @t3 
      when '*' then @t1 * @t3 
      when '/' then @t1/@t3 
     end 
    ); 

    return @t1; 
end; 

而且它適用於下一個數據:

select dbo.eval('7.6*11.3/4.5') as eval, 7.6*11.3/4.5 as sqlServerCalc; 

eval     sqlServerCalc 
19,0844444444444  19.084444 

後,您可以通過列值的公式中替換值,並計算出它:

with cte as (
    select rn, t1, t2, t3, formula, 
     REPLACE(REPLACE(REPLACE(formula, 't1', cast(t1 as varchar(max))), 't2', cast(t2 as varchar(max))), 't3', cast(t3 as varchar(max))) as calc 
    from db_test.dbo.loop 
) 
select rn, t1, t2, t3, formula, db_test.dbo.eval(calc) as result 
into db_test.dbo.loop2 
from cte; 

時間對我來說沒問題,我的Sql Server 2016需要3分鐘,並且效果很好:

select top 5 * 
from db_test.dbo.loop2; 
rn t1 t2 t3 formula   result 
1 80 23 93 t1/t2 * t3 323,478260869565 
2 80 87 30 t1/t2 * t3 27,5862068965517 
3 92 83 63 t1/t2 * t3 69,8313253012048 
4 68 19 36 t1/t2 * t3 128,842105263158 
5 65 63 10 t1/t2 * t3 10,3174603174603 

如果您有公式中適用的所有操作的列表,則可以爲多個變量編寫一個通用函數。但是,如果公式中有更復雜的部分,那麼應該使用CLR。