2017-08-29 39 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。