在 SQL Server 中使用插入语句优化必要的 while 循环

Posted

技术标签:

【中文标题】在 SQL Server 中使用插入语句优化必要的 while 循环【英文标题】:Optimize a necessary while loop with insert statement in SQL Server 【发布时间】:2021-04-19 16:34:21 【问题描述】:

我有一系列在 while 循环中运行一些数学运算的程序。他们正在计算运行平均值,其中知道先前计算的值对于获得下一个值是绝对必要的。一个项目与一组值配对,按日期排序,并使用 10 种不同的方法来计算运行平均值。

提前回答一些问题:每次运行的每个 id 的数组长度都是相同的,但如果数据丢失,则可能存在空值。我事先不知道正在计算滚动平均值的 id 数量,并且每次都会有所不同。我事先不知道必要的迭代次数,每次都会有所不同。尽管在微积分上工作了一年多,但我还没有找到一种数学方法来将系统扁平化为一个选择/加入——5 个是非线性的,而且我在大学里只有 4 个学期的微积分,所以我无法理解出一个扁平化的解决方案(我为我正在工作的 10 种方法中的 5 种做了,但剩下的 5 种要么太复杂,要么需要事先了解值到达的顺序——每次它们都是不同的运行)。

我的代码运行了,使用 while 循环的 5 个方法确实完成了,但是每个方法运行 5000 个项目(所有项目都有自己的迭代和计算)大约需要 15 分钟到 30 分钟。我需要能够扩展到 300000 个项目,所以 15 分钟是站不住脚的,特别是因为这是在数据库中运行的数十个过程之一。

下面是我的其中一种方法的代码示例。我不是在数学部分寻求帮助(因为需要循环的 5 个过程中的每一个的数学都不同),而是在用作循环背后意图的插入语句中:

create proc analysis.calculateavg_logarithmicshifted
    @maxi int --max number of iterations determined by separate proc
as
    
declare @icount int = 1;
declare @n int;
select @n = max(n) from #temp_pink; --comes from the procedure that calls this one, and is a way to identify the max array size
    
drop table if exists analysis.avglogarithmicshifted;
    
create table analysis.avglogarithmicshifted(    
    id nvarchar(64),    
    i int,  
    mu decimal(19, 6),  
    insertdate datetime,    
    avgname nvarchar(64)    
);
    
drop table if exists #temp_k;

--Pull only the historic data from the source
select  
    id, 
    k,  
    price
into #temp_k
from #temp_bbfull --original list of data, n elements per id
where history = 1;
    
while @icount <= @maxi  
    begin;      

    drop table if exists #temp_premu;
    
    --Calculate one sub-section of the rolling average
    select          
        id,         
        sum(log(price)) / (@n - sum(case when price is null then 1 else 0 end)) as premu        
    into #temp_premu        
    from #temp_k        
    where k between @icount and @icount + @n
    group by id;        
    
    drop table if exists #temp_f;           
    
    --Calculate the main component of the rolling average
    select          
        k.id,           
        @icount + @n as k,          
        exp(p.premu + (sum(power(log(k.price) - p.premu, 2)) / (2 * (@n - sum(case when k.price is null then 1 else 0 end))))) as price     
    into #temp_f        
    from #temp_k k          
    join #temp_premu p              
        on p.id= k.id           
    join #temp_bbfull bb                
        on bb.id= k.id              
        and bb.k = k.k      
    where k.k between @icount 
        and @icount + @n
    group by k.id, p.premu;         
    
    --Insert this iteration's rolling average into the table with incremented identifier k
    insert into #temp_k (id, k, price)      
    select          
        *       
    from #temp_f        
    where price is not null;        
    
    select @icount = @icount + 1;   
end;

--Insert final aggregated data into destination table
insert into analysis.avglogarithmicshifted (id, i, mu, insertdate, avgname)
select  
    k.id,   
    k.k - @n as I,  
    k.price as mu,  
    getdate() as insertdate,    
    'Logarithmic Shifted' as avgname
from #temp_k k
where k.k > @n  
    and k.price is not null;

名称和标识符已从我的原始代码中更改,但没有其他内容。任何帮助将不胜感激。

我使用的是 SQL Server 17.9.1

【问题讨论】:

老实说WHILE真的有必要吗?在 SQL 中很少需要WHILE非常很少。我怀疑你真的需要一个。 “我使用的是 SQL Server 17.9.1” 没有这样的东西; SQL Server 的最新版本是 15.0.4083.2(即 SQL Server 2019 RTM-CU8-GDR)。 我们已经通过使用 SQL-CLR 聚合函数而不是 SUM(LOG... 来优化这个演算 那么仅仅是您所做的 TRUNCATE 更改吗?你添加了什么索引?我在这里在黑暗中工作。解决这些问题的(继续)技巧是找到最昂贵的语句。启动 Profiler。使用模板 TSQL_SPs 并为您正在运行您的 proc 的 SPID 添加一个过滤器。这将告诉您哪些语句花费的时间最长,但也会增加开销。找到耗时最长的语句并对其进行调整。因为你在一个循环中,这将产生乘数积极的影响。 您使用哪种类型的卷(记录数)?每列中有哪些值?作为参数传入的值范围是多少? 1-100, 1-1,000,000 ? 【参考方案1】:

总的来说,如果不对代码进行重大重新设计,我认为您将无法获得接近 100 倍(2 个数量级)的改进。我不了解您的代码逻辑,因此无能为力,只能说 SQL Server 2016 支持允许基于集合计算移动平均线的窗口函数。这个链接可能是help

以下是一些优化它的技巧:

在连接多个临时表时,确保包含大量数据的表 有索引。 尽量减少用于存储中间计算结果的临时表的数量。 确保中间临时表中的行数尽可能少。

从您给出的示例中,我可以看到一些可能的优化。由于我没有数据,我无法检查它们是否有帮助:

0 #temp_bbfull - 看起来这个表是用作数据源的,有很多数据。您必须有正确的索引。

CREATE INDEX IX_temp_bbfull ON #temp_bbfull( id, k )

1 尽可能限制你的工作集

select  id, k, price
into #temp_k
from #temp_bbfull --original list of data, n elements per id
-- As you are restricting iterations you might as well restrict the number of rows upfront
where history = 1 and k between @icount and @maxi;

2 这可能有帮助,也可能没有帮助。

CREATE INDEX IX_temp_k ON temp_k( id )

3 组合查询

select          
    k.id, @icount + @n as k,          
    exp(p.premu + (sum(power(log(k.price) - p.premu, 2)) / (2 * (@n - sum(case when k.price is null then 1 else 0 end))))) as price     
into #temp_f        
from #temp_k as k          
    join (
         select id, sum(log(price)) / (@n - sum(case when price is null then 1 else 0 end)) as premu        
         from #temp_k        
         where k between @icount and @icount + @n
         group by id ) as p
     on p.id= k.id
    join #temp_bbfull as bb on bb.id= k.id and bb.k = k.k      
where k.k between @icount and @icount + @n
group by k.id, p.premu;

等等。

【讨论】:

我可以缩小我使用的集合的大小,但不能缩小我需要每周运行的 ID 总数(它只会增长)。组合查询可能会有所帮助,但可能仅在我用作示例的方法中,因为其他 4 个具有不同的数学结构。查询计划和分析显示插入是处理时间和使用方面的杀手。我会尝试索引方法,看看是否有帮助,尽管从这一切来看,我认为我最大的希望在于数学家解决我的非线性展平问题。 插入通常是 SQL Server 中最快的操作。您确定插入是问题,而不是产生结果的选择吗?如果插入确实是一个问题,请检查您的 tempDB 是否正确设置(有很多文章介绍了如何设置它以获得最佳性能) 查看窗口函数。我认为您的逻辑(至少其中一些)可以迁移以使用它们:red-gate.com/simple-talk/sql/t-sql-programming/… 我对窗口函数非常熟悉,但是因为我不是在计算现有数据集的滚动平均值,而是在滚动时创建的数据上使用滚动平均值 -我很确定前面/之间的行(以及领先/滞后等)将不起作用。我考虑过进行窗口递归,但由于在开始之前我不知道最终的迭代次数,并且 maxrecursion 不允许变量,我认为它可能会导致一些主要的内部冲突,但值得测试。 所以结果表明聚合函数在递归中是不允许的,所以我看不到让 windows 工作的方法,因为第一次之后的任何迭代都依赖于已经计算过的先前值。跨度>

以上是关于在 SQL Server 中使用插入语句优化必要的 while 循环的主要内容,如果未能解决你的问题,请参考以下文章

Sql Server 优化----SQL语句的执行方式与锁以及阻塞的关系

Sql Server 优化----SQL语句的执行方式与锁以及阻塞的关系

强制SQL Server执行计划使用并行提升在复杂查询语句下的性能

SQL语句优化

sql server而不是使用插入的变量和值触发插入语句

SQL Server插入数据和删除数据基础语句使用