Home > Blockchain >  SQL (Snowflake) - Column that recursively refers to itself
SQL (Snowflake) - Column that recursively refers to itself

Time:05-17

I have seen a number of posts on this topic but I couldn't find any that are exactly the same.

I would like to make a view with a column that refers to itself. In the example below:

  • Value 1 2 are static values that continue throughout the view.
  • Value 3 has data that updates so I want it to do Value 3 (Value 1 - Value 2) when there is data in Value 3.
  • When Value 3 has no data, use it's prior value to calculate.
Index Value1 Value2 Value3 Calculation
1 0.5 0.25 1 1.25
2 0.5 0.25 1.5
3 0.5 0.25 1.75

Is this possible? So far I have got it to calculate the row where index = 2 using LAG or Recursive CTE's but can't seem to work out how to get it to continue calculating.

CodePudding user response:

With slightly extended example data to show a new value3 taking precedent:

WITH data(Index, Value1, Value2, Value3) AS (
    SELECT * FROM VALUES   
    (1, 0.5, 0.25,  1),
    (2, 0.5, 0.25,  null),
    (3, 0.5, 0.25,  null),
    (4, 0.5, 0.25,  10),
    (5, 0.5, 0.25,  null),
    (6, 0.5, 0.25,  null),
    (7, 0.5, 0.25,  null)
)
select d.*
    ,iff(d.value3 is not null, d.index, null) as idx_k
    ,lag(d.value3) ignore nulls over(order by d.index) as l_value3
    ,lag(idx_k) ignore nulls over(order by d.index) as l_idx_k
    ,d.index - iff(idx_k is not null, idx_k, l_idx_k) 1 as r_d
    ,iff(d.value3 is not null, d.value3, l_value3)   r_d*(d.value1-d.value2) as calc
from data as d
order by 1;

gives:

INDEX VALUE1 VALUE2 VALUE3 IDX_K L_VALUE3 L_IDX_K R_D CALC
1 0.5 0.25 1 1 1 1.25
2 0.5 0.25 1 1 2 1.5
3 0.5 0.25 1 1 3 1.75
4 0.5 0.25 10 4 1 1 1 10.25
5 0.5 0.25 10 4 2 10.5
6 0.5 0.25 10 4 3 10.75
7 0.5 0.25 10 4 4 11

which can be smashed together into:

select d.*
    ,iff(d.value3 is not null, d.value3, lag(d.value3) ignore nulls over(order by d.index))   (d.index - iff(d.value3 is not null, iff(d.value3 is not null, d.index, null), lag(iff(d.value3 is not null, d.index, null)) ignore nulls over(order by d.index)) 1)*(d.value1-d.value2) as calc
from data as d

if you really want, but I would be more inclined to wrap that in a sub-select just to make it present nicer:

select index, value1, value2, value3, calc 
from (
    select d.*
        ,iff(d.value3 is not null, d.index, null) as idx_k
        ,lag(d.value3) ignore nulls over(order by d.index) as l_value3
        ,lag(idx_k) ignore nulls over(order by d.index) as l_idx_k
        ,d.index - iff(idx_k is not null, idx_k, l_idx_k) 1 as r_d
        ,iff(d.value3 is not null, d.value3, l_value3)   r_d*(d.value1-d.value2) as calc
    from data as d
)
order by 1

Recursive CTE:

WITH recursive r_cte as (
    select index, value1, value2, value3, value3 as calc
    from data 
    where index = 1
    
    union all
    
    select d.index, d.value1, d.value2, d.value3, iff(d.value3 is null, r.calc, d.value3)   d.value1 - d.value2 as calc
    from r_cte as r
    join data d on r.index   1 = d.index
)
select * from r_cte
  • Related