Best way to interpolate values in SQL

前端 未结 3 1103
孤独总比滥情好
孤独总比滥情好 2020-12-15 08:16

I have a table with rate at certain date :

          Rates

Id  |     Date      |  Rate
----+---------------+-------
 1  |   01/01/2011  |  4.5
 2  |   01/04         


        
相关标签:
3条回答
  • 2020-12-15 08:24

    The trick with CROSS JOIN here is it wont return any records if either of the table does not have rows (1 * 0 = 0) and the query may break. Better way to do is use FULL OUTER JOIN with inequality condition (to avoid getting more than one row)

    ( SELECT TOP 1 
            Date, Rate 
        FROM Rates
        WHERE Date <= @InputDate
        ORDER BY Date DESC
      ) AS prev
      FULL OUTER JOIN
      ( SELECT TOP 1 
            Date, Rate 
        FROM Rates
        WHERE Date >= @InputDate
        ORDER BY Date ASC
      ) AS next
    ON (prev.Date <> next.Date) [or Rate depending on what is unique]
    
    0 讨论(0)
  • 2020-12-15 08:28

    Something like this (corrected):

    SELECT CASE WHEN next.Date IS NULL  THEN prev.Rate
                WHEN prev.Date IS NULL  THEN next.Rate
                WHEN next.Date = prev.Date  THEN prev.Rate
                  ELSE ( DATEDIFF(d, prev.Date, @InputDate) * next.Rate 
                       + DATEDIFF(d, @InputDate, next.Date) * prev.Rate
                       ) / DATEDIFF(d, prev.Date, next.Date)
           END AS interpolationRate 
    FROM
      ( SELECT TOP 1 
            Date, Rate 
        FROM Rates
        WHERE Date <= @InputDate
        ORDER BY Date DESC
      ) AS prev
      CROSS JOIN
      ( SELECT TOP 1 
            Date, Rate 
        FROM Rates
        WHERE Date >= @InputDate
        ORDER BY Date ASC
      ) AS next
    
    0 讨论(0)
  • 2020-12-15 08:40

    As @Mark already pointed out, the CROSS JOIN has its limitations. As soon as the target value falls outside the range of defined values no records will be returned.

    Also the above solution is limited to one result only. For my project I needed an interpolation for a whole list of x values and came up with the following solution. Maybe it is of interested to other readers too?

    -- generate some grid data values in table #ffffd:
    CREATE TABLE #ffffd (id int,x float,y float, PRIMARY KEY(id,x));
    INSERT INTO  #ffffd VALUES (1,3,4),(1,4,5),(1,6,3),(1,10,2),
                             (2,1,4),(2,5,6),(2,6,5),(2,8,2);
    SELECT * FROM #ffffd;                         
    
    -- target x-values in table #vals (results are to go into column yy):
    CREATE TABLE #vals (xx float PRIMARY KEY,yy float null, itype int);
    INSERT INTO  #vals (xx) VALUES (1),(3),(4.3),(9),(12);
    
    -- do the actual interpolation
    WITH valstyp AS (
      SELECT id ii,xx,
             CASE WHEN min(x)<xx THEN CASE WHEN max(x)>xx THEN 1 ELSE 2 END ELSE 0 END flag,
             min(x) xmi,max(x) xma 
      FROM #vals INNER JOIN #ffffd ON id=1 GROUP BY xx,id
    ), ipol AS (
      SELECT v.*,(b.x-xx)/(b.x-a.x) f,a.y ya,b.y yb 
      FROM valstyp v 
      INNER JOIN #ffffd a ON a.id=ii AND a.x=(SELECT max(x) FROM #ffffd WHERE id=ii 
                 AND (flag=0 AND x=xmi OR flag=1 AND x<xx OR flag=2 AND x<xma))
      INNER JOIN #ffffd b ON b.id=ii AND b.x=(SELECT min(x) FROM #ffffd WHERE id=ii 
                 AND (flag=0 AND x>xmi OR flag=1 AND x>xx OR flag=2 AND x=xma))
    )
    UPDATE v SET yy=ROUND(f*ya+(1-f)*yb,8),itype=flag FROM #vals v INNER JOIN ipol i ON i.xx=v.xx;
    
    -- list the interpolated results table:
    SELECT * FROM #vals
    

    When running the above script you will get the following data grid points in table #ffffd

    id x  y 
    -- -- - 
    1  3  4 
    1  4  5 
    1  6  3 
    1  10 2 
    2  1  4 
    2  5  6 
    2  6  5 
    2  8  2 
    

    [[ The table contains grid points for two identities (id=1 and id=2). In my example I referenced only the 1-group by using where id=1 in the valstyp CTE. This can be changed to suit your requirements. ]]

    and the results table #vals with the interpolated data in column yy:

    xx  yy   itype 
    --- ---- ----- 
    1   2    0     
    3   4    0     
    4.3 4.7  1     
    9   2.25 1     
    12  1.5  2     
    

    The last column itype indicates the type of interpolation/extrapolation that was used to calculate the value:

    0:  extrapolation to lower end
    1:  interpolation within given data range
    2:  extrapolation to higher end
    

    This working example can be found here.

    0 讨论(0)
提交回复
热议问题