更新表中的每一行

时间:2017-08-29 09:39:24

标签: sql sql-server function tsql

我有一个包含一些值列,公式和结果列的表。

|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到1000万。

有没有办法加快速度?解决这个问题的替代方法是什么?

非常感谢!

6 个答案:

答案 0 :(得分: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]

答案 1 :(得分:1)

想到两件简单的事情:

  1. 如果您要分别更新每一行,请确保rownum列上有索引。

  2. 如果只有少数不同的公式,您可以在一个UPDATE中使用相同的公式更新所有行,而不是单独更新每一行。在这种情况下,formula列上的索引会有所帮助。

答案 2 :(得分: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)

答案 3 :(得分:0)

使用触发器选项,但就目前而言,更新块的影响会更小。

TOP(5000)每次只会更新5000行WHERE [result] is null OR [result]=''

GO 20000将执行此查询20000次(1000万行)它将继续执行,直到返回UPDATE语句的0条记录。

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

在此之后,创建触发器

答案 4 :(得分:0)

感谢所有的回复和想法。最后,通过在维度而不是事实表中保存公式来解决问题。 这将为维度中的每一行生成1个更新语句,并将其应用于具有where子句的所有相关事实行,而不是每个事实行的1个更新语句。 处理时间从>开始下降。 1.5小时到不到一秒钟。

答案 5 :(得分: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

如果您确定,所有公式都是有效的,并且您不会将除以零或数据类型溢出,在这种情况下,您可以在SQL Server中创建自己的eval()函数。

我在公式中为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。