编写迭代自引用SQL查询
我需要在SQL中实现一个复杂的算法,该算法需要能够为第一行计算某个值,并且在所有后续行上,需要能够使用前一行的计算值作为其自身计算的一部分。它是递归的,但从包含所有空值的目标列的位置开始 我使用下面的光标简化了解决此问题的尝试:编写迭代自引用SQL查询,sql,sql-server,tsql,Sql,Sql Server,Tsql,我需要在SQL中实现一个复杂的算法,该算法需要能够为第一行计算某个值,并且在所有后续行上,需要能够使用前一行的计算值作为其自身计算的一部分。它是递归的,但从包含所有空值的目标列的位置开始 我使用下面的光标简化了解决此问题的尝试: if object_id('tempdb..#t') is not null drop table #t; -- create a simple demo table containing IDs 1 - 10 and an empty int column
if object_id('tempdb..#t') is not null
drop table #t;
-- create a simple demo table containing IDs 1 - 10 and an empty int column
with cte as (
select x = 1
union all
select x = x + 1
from cte
where x < 10
)
select *, WriteVal = cast(null as int)
into #t
from cte
declare c cursor for
select x from #t
declare @x int
open c
fetch next from c into @x
while @@FETCH_STATUS = 0
begin
declare @WriteVal int
-- Set @WriteVal to the previous row's [WriteVal] + 10. If previous row's [WriteVal] is NULL,
-- (i.e. we're in the first row), use 0 + 10 instead.
select @WriteVal = isnull(lag(WriteVal, 1) over (order by x), 0) + 10
from #t
where x = @x
-- Update this row on the temp table with the value stored in @WriteVal
update #t
set WriteVal = @WriteVal
where x = @x
-- return the full table for debugging
select *, [@WriteVal] = @WriteVal
from #t
fetch next from c into @x
end
close c
deallocate c
我希望看到的是:
lag(WriteVal,1)
为空,因此@WriteVal
被设置为10,然后10
被写入第1行的[WriteVal]
lag(WriteVal,1)
是10,因此@WriteVal
被设置为(10+10)20,然后20
被写入第2行的[WriteVal]
lag(WriteVal,1)
是20,因此@WriteVal
被设置为(20+10)30,然后30
被写入第3行的[WriteVal]
x | WriteVal
----|---------
1 | 10
2 | 20
3 | 30
4 | 40
...
10 | 100
x | WriteVal
----|---------
1 | 10
2 | 10
3 | 10
4 | 10
...
10 | 10
实际返回的是将every[WriteVal]
设置为10
,进一步检查将表明后续循环迭代无法识别更新的前一行值,因此lag(WriteVal,1)
始终返回NULL。我认为这要归咎于某些优化或缓存机制,或者在查询完成或其他情况下更新才真正提交
实际结果集:
x | WriteVal
----|---------
1 | 10
2 | 20
3 | 30
4 | 40
...
10 | 100
x | WriteVal
----|---------
1 | 10
2 | 10
3 | 10
4 | 10
...
10 | 10
我如何解决这个问题?有更好的方法吗?如果可能的话,我宁愿避免使用游标,但它需要能够首先计算第n行,然后使用该结果计算第n+1行,这意味着我在使用自联接或窗口函数的非游标解决方案方面没有任何运气。根据您的示例数据,您希望:
select x,
10 * row_number() over (order by x) as writeval
from #t;
要更新值,请使用可更新的CTE:
with toupdate as (
select t.*,
10 * row_number() over (order by x) as new_writeval
from #t t
)
update toupdate
set writeval = new_writeval;
请注意,您的查询返回了不一致的结果,因为您的游标没有使用
order by
,因此结果可以在调用之间更改。示例数据、所需结果以及对您想要完成的操作的清晰解释确实会有所帮助。如果这是递归的,我怀疑您是否需要LAG
。一般来说,您从对递归CTE的回调中得到“previous”值。@GordonLinoff实际的数据集非常庞大且非常广泛(甚至仅考虑算法所需的列),而实际的算法非常复杂,需要大量的上下文,所以我故意把它归结为问题的根源——否则这个问题会长好几英里,主要的实际问题会被完全掩盖。此外,这个问题解释了期望的结果,并解释了我试图实现的目标。如果你能在我提交问题后15秒内收到你的评论,如果你能抽出时间先阅读,我将不胜感激。@GordonLinoff为他们辩护说:“如果你能在我提交问题后15秒内收到你的评论,他们在SQL方面非常有经验;对于我们这些人来说,回答一个确实需要样本数据和预期结果的问题通常非常容易,我同意他们的说法(我也没有花超过15秒的时间得出这个结论)。它不必是您的完整数据集,这就是为什么它被称为“示例”。仅仅是一个小的代表性数据集,以及您对该数据的预期结果,将极大地帮助我们帮助您。或者从#tt中选择(x-1)*10?