Лучший способ интерполировать значения в SQL - PullRequest
13 голосов
/ 04 июля 2011

У меня есть таблица со скоростью на определенную дату:

          Rates

Id  |     Date      |  Rate
----+---------------+-------
 1  |   01/01/2011  |  4.5
 2  |   01/04/2011  |  3.2
 3  |   04/06/2011  |  2.4
 4  |   30/06/2011  |  5

Я хочу получить базу выходных данных с помощью простой линейной интерполяции.

Так что, если я введу 17/06 /2011:

Date        Rate
----------  -----
01/01/2011  4.5
01/04/2011  3.2
04/06/2011  2.4
17/06/2011  
30/06/2011  5.0

линейная интерполяция равна (5 + 2,4) / 2 = 3,7

Есть ли способ сделать простой запрос (SQL Server 2005), или такого рода вещи должны быть выполнены впрограммным способом (C # ...)?

Ответы [ 3 ]

21 голосов
/ 11 июля 2011

Примерно так (исправлено):

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
5 голосов
/ 08 марта 2016

Как уже отмечал @Mark, у CROSS JOIN есть свои ограничения. Как только целевое значение выходит за пределы диапазона определенных значений, никакие записи не возвращаются.

Также вышеупомянутое решение ограничено только одним результатом. Для моего проекта мне потребовалась интерполяция для всего списка значений x, и я нашел следующее решение. Может быть, это интересно и другим читателям?

-- generate some grid data values in table #ddd:
CREATE TABLE #ddd (id int,x float,y float, PRIMARY KEY(id,x));
INSERT INTO  #ddd 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 #ddd;                         

-- 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 #ddd 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 #ddd a ON a.id=ii AND a.x=(SELECT max(x) FROM #ddd WHERE id=ii 
             AND (flag=0 AND x=xmi OR flag=1 AND x<xx OR flag=2 AND x<xma))
  INNER JOIN #ddd b ON b.id=ii AND b.x=(SELECT min(x) FROM #ddd 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

При запуске вышеуказанного скрипта вы получите следующие точки сетки данных в таблице #ddd

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 

[[Таблица содержит точки сетки для двух тождеств (id=1 и id=2). В моем примере я ссылался только на 1 -группу, используя where id=1 в valstyp CTE. Это может быть изменено в соответствии с вашими требованиями. ]]

и таблица результатов #vals с интерполированными данными в столбце yy:

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

Последний столбец itype указывает тип интерполяции / экстраполяции, который использовался для вычисления значения:

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

Этот рабочий пример можно найти здесь .

5 голосов
/ 26 января 2012

Хитрость в CROSS JOIN заключается в том, что он не будет возвращать никаких записей, если ни в одной из таблиц нет строк (1 * 0 = 0) и запрос может прерваться. Лучший способ сделать это - использовать FULL OUTER JOIN с условием неравенства (чтобы избежать получения более одной строки)

( 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]
...