2017-08-29 73 views
5

Mam tabelę z kolumnami wartości, formułą i kolumną wyników.Zaktualizuj każdy wiersz w tabeli

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

Chcę wypełnić kolumnę result z wynikiem formuły.

Obecnie robię to z tego zapytania:

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) 

Problemem jest to, że zajmuje bardzo dużo czasu. Liczba wierszy w tabeli wyniesie 5-10 milionów.

Czy jest jakiś sposób, aby to przyspieszyć? Alternatywne podejście do tego problemu?

Wielkie dzięki!

+0

Możesz opublikować tę wiadomość w [Przeglądzie kodu] (https://codereview.stackexchange.com/). To lepsze miejsce do pracy z kodem, który chcesz przyspieszyć. –

+0

jest formuła akt obliczona kolumna? –

+0

Musisz szukać wąskich gardeł w swojej 'aktualizacji'. Czy masz jakieś wyzwalacze, indeksy lub FK? Jakiego rodzaju izolacji używasz? Zobacz także plan zapytania. –

Odpowiedz

0

Dzięki za wszystkie odpowiedzi i pomysłów. Ostatecznie problem został rozwiązany przez zapisanie formuły w wymiarze zamiast w tabeli faktów. Generuje to 1 instrukcję aktualizacji dla każdej linii w wymiarze i stosuje ją do wszystkich odpowiednich wierszy faktów z klauzulą ​​where, w przeciwieństwie do 1 instrukcji aktualizacji dla każdego wiersza faktów. Czas procesu spadł z> 1,5 godziny do mniej niż sekundy.

3

Przyjmując zasady kolejności operator i obejmujące tylko swój prosty przykład wzoru:

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

dziękuję, ale sposób, w jaki przedstawiłem problem jest bardzo uproszczony. W rzeczywistości jest 7 pól. Formuła składa się z 1 lub więcej z tych pól i może być zbudowana w dowolny sposób. Nie ma żadnych ograniczeń co do struktury wzoru poza tym, że musi być matematycznie poprawny. – tv87

+1

@ tv87 Dodałem kolejną odpowiedź, która może pomóc. – cloudsafe

1

dwóch prostych rzeczy, które przychodzą na myśl:

  1. Upewnij się, że wskaźnik na rownum kolumnie jeśli jesteś aktualizowanie każdego wiersza indywidualnie.

  2. Jeśli istnieje tylko kilka różnych formuł, można zaktualizować wszystkie wiersze o tej samej formule w jednym UPDATE zamiast aktualizować każdy wiersz indywidualnie. W takim przypadku pomocny mógłby być indeks na kolumnie formula.

+0

Witam, dodałem indeks na kolumnie rownum, która przyspieszyła zapytanie. Niestety nadal jest dużo wolniej. Liczba różnych używanych formuł jest ograniczona i zależy od wymiaru powiązanego z tą tabelą. Więc może dla 1 miliona linii istnieje 100 unikalnych formuł do zastosowania. Spróbuję przenieść formuły do ​​tej tabeli wymiarów i pobrać ją stamtąd, aby ograniczyć ilość instrukcji aktualizacji, które muszą zostać wykonane. – tv87

+0

@ tv87, zdecydowanie szybciej jest uruchomić 'UPDATE' 100 razy, niż 1 000 000 razy. Zwłaszcza, gdy całkowita liczba zaktualizowanych wierszy jest taka sama. Powinien być prawie 10 000 razy szybszy. Posiadanie indeksu w kolumnie "formuła" jest jednak ważne. –

1

Czy to szybsza aktualizacja zbiorcza według typów formuł? Indeks wymagany na [wzór] również:

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

Idź z opcją wyzwalania, ale na razie aktualizacja w częściach będzie mniejszy wpływ.

TOP(5000) zaktualizuje właśnie 5000 rzędach czas WHERE [result] is null OR [result]=''

GO 20000 będzie wykonywał tego zapytania 20000 razy (10 mln wierszach) będzie nadal wykonywać aż zwraca 0 rekordów dla instrukcji UPDATE.

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 

Po tym, stworzyć spust

0

Właśnie utworzyłem tabelę z 5 milionami wierszy. Ze struktury tabeli jako:

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 

Jeżeli jesteś pewien, że wszystko, co wzory są ważne, a nie będziesz musiał dzielenie przez zero, na przykład, lub typ danych przelewu, w tym przypadku można zrobić własny eval() funkcja w serwerze SQL.

Stworzyłem własną funkcję dla 3 wartości we wzorze ze znakami takimi jak: "+", "-", "*", "/".

Kod funkcji wynosi:

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; 

i działa na kolejnych danych:

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 

Po tym można zastąpić wartości w formule o wartości kolumn i obliczyć:

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; 

Czas jest dla mnie w porządku, zajmuje 3 minuty na moim serwerze Sql Server 2016 i daje dobre wyniki:

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 

Jeśli masz listę wszystkich operacji, które mają zastosowanie w formule, możesz napisać wspólną funkcję dla wielu zmiennych. Ale jeśli w formule jest coś bardziej skomplikowanego, należy użyć CLR.