簡體   English   中英

編寫迭代自引用 SQL 查詢

[英]Writing an iterative self-referential SQL query

我有一個復雜的算法需要在 SQL 中實現,其中算法需要能夠為第一行計算某個值,並且在所有后續行上都需要能夠使用前一行的計算值作為其自身的一部分計算。 它是遞歸的,但從包含所有 NULL 的目標列的 position 開始。

我在下面使用 cursor 簡化了解決此問題的嘗試:

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

起始數據集:

x   | WriteVal
----|---------
1   | NULL
2   | NULL
3   | NULL
4   | NULL
...
10  | NULL

我期待看到的是:

  1. 在第一次循環迭代中, lag(WriteVal, 1)是 NULL,所以@WriteVal設置為 10,然后將10寫入第 1 行的[WriteVal]
  2. 在第二次循環迭代中, lag(WriteVal, 1)為 10,因此@WriteVal設置為 (10 + 10) 20,然后將20寫入第 2 行的[WriteVal]
  3. 在第三次循環迭代中, lag(WriteVal, 1)為 20,因此@WriteVal設置為 (20 + 10) 30,然后將30寫入第 3 行的[WriteVal]
  4. ... 等等...

預期結果集:

x   | WriteVal
----|---------
1   | 10
2   | 20
3   | 30
4   | 40
...
10  | 100

實際返回的是每個[WriteVal]設置為10 ,進一步檢查將表明后續循環迭代無法識別更新的前一行的值,因此lag(WriteVal, 1)總是返回 NULL。 我認為某些優化或緩存機制應歸咎於此,或者直到查詢完成或其他事情才真正提交更新。

實際結果集:

x   | WriteVal
----|---------
1   | 10
2   | 10
3   | 10
4   | 10
...
10  | 10

我該如何解決這個問題? 有更好的方法嗎? 如果可能的話,我寧願避免使用游標,但事實上它需要能夠首先計算第n行,然后將結果用於第n + 1行,這意味着我對使用 self 的非游標解決方案沒有任何運氣-joins 或 window 函數

根據您的樣本數據,您需要:

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;

請注意,您的查詢返回的結果不一致,因為您的 cursor 不使用order by ,因此結果可能會在調用之間發生變化。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM