Интерполяция между двумя значениями в одном запросе - PullRequest
0 голосов
/ 25 октября 2011

Я хочу вычислить значение путем интерполяции значения между двумя ближайшими соседями.У меня есть подзапрос, который возвращает значения соседей и их относительное расстояние в виде двух столбцов с двумя элементами.

Допустим,

(select ... as value, ... as distance 
 from [get some neighbours by distance] limit 2) as sub

Как вычислить значение точки с помощью линейной интерполяции?Возможно ли это сделать в одном запросе?

Пример: Моя точка имеет соседа A со значением 10 на расстоянии 1 и соседа B со значением 20 на расстоянии 4.функция должна вернуть значение 10 * 4 + 20 * 1 / 5 = 12 для моей точки.

Я попробовал очевидный подход

select sum(value * (sum(distance)-distance)) / sum(distance)

, который потерпит неудачу, потому что вы не можете работать с предложениями группы внутри предложений группы.Использование другого подзапроса, возвращающего сумму, также невозможно, потому что тогда я не могу переслать отдельные значения одновременно.

Ответы [ 2 ]

1 голос
/ 25 октября 2011

Это безобразный хак (основанный на злоупотреблении CTE;).Суть в том, что

value1 * distance2 + value2 * distance1

можно, перемножив на distance1 * distance2, переписать в

value1/distance1 + value2/distance2

Таким образом, продукты (или подразделения) могут оставаться внутри своих рядов.После суммирования умножение на (distance1 * distance2) изменяет результат до желаемого результата.Обобщение для более чем двух соседей оставлено читателю в качестве упражнения. YMMV

DROP TABLE tmp.points;
CREATE TABLE tmp.points
    ( pname VARCHAR NOT NULL PRIMARY KEY
    , distance INTEGER NOT NULL
    , value INTEGER
    );

INSERT INTO tmp.points(pname, distance, value) VALUES
  ( 'A' , 1, 10 )
,  ( 'B' , 4, 20 )
,  ( 'C' , 10 , 1)
,  ( 'D' , 11 , 2)
  ;
WITH RECURSIVE twin AS (
    select 1::INTEGER AS zrank
    , p0.pname AS zname
    , p0.distance AS dist
    , p0.value AS val
    , p0.distance* p0.value AS prod
    , p0.value::float / p0.distance AS frac
    FROM tmp.points p0
    WHERE NOT EXISTS ( SELECT * FROM tmp.points px
        WHERE px.distance < p0.distance)
    UNION
    select 1+twin.zrank AS zrank
    , p1.pname AS zname
    , p1.distance AS dist
    , p1.value AS val
    , p1.distance* p1.value AS prod
    , p1.value::float / p1.distance AS frac
    FROM tmp.points p1, twin
    WHERE p1.distance > twin.dist
    AND NOT EXISTS ( SELECT * FROM tmp.points px
        WHERE px.distance > twin.dist
        AND px.distance < p1.distance
        )   
    )
-- SELECT * from twin ;
SELECT min(zname) AS name1, max(zname) AS name2
    , MIN(dist) * max(dist) *SUM(frac) / SUM(dist) AS score
    FROM twin
    WHERE zrank <=2
    ;

Результат:

CREATE TABLE
INSERT 0 4
 name1 | name2 | score 
-------+-------+-------
 A     | B     |    12

Обновление: это немного cleaner ... связи по-прежнему не обрабатываются (для этого нужна оконная функция или предложение LIMIT 1 во внешнем запросе)

WITH RECURSIVE twin AS (
    select 1::INTEGER AS zrank
    , p0.pname AS name1
    , p0.pname AS name2
    , p0.distance AS dist
    FROM tmp.points p0
    WHERE NOT EXISTS ( SELECT * FROM tmp.points px
        WHERE px.distance < p0.distance)
    UNION
    select 1+twin.zrank AS zrank
    , twin.name1 AS name1
    , p1.pname AS name2
    , p1.distance AS dist
    FROM tmp.points p1, twin
    WHERE p1.distance > twin.dist
    AND NOT EXISTS ( SELECT * FROM tmp.points px
        WHERE px.distance > twin.dist
        AND px.distance < p1.distance
        )
    )
SELECT twin.name1, twin.name2
    , (p1.distance * p2.value + p2.distance * p1.value) / (p1.distance+p2.distance)  AS score
    FROM twin
    JOIN tmp.points p1 ON (p1.pname = twin.name1)
    JOIN tmp.points p2 ON (p2.pname = twin.name2)
    WHERE twin.zrank =2
    ;
1 голос
/ 25 октября 2011

Если вы действительно хотите, чтобы point между ними, есть встроенный способ сделать это (но не агрегатная функция):

SELECT center(box(x.mypoint,y.mypoint))
FROM   ([get some neighbours by distance] order by value limit 1) x
      ,([get some neighbours by distance] order by value offset 1 limit 1) y;

Если вы хотите среднее расстояние:

SELECT avg(x.distance)
FROM   ([get some neighbours by distance] order by value limit 2) as x

См. геометрические функции и агрегатные функции в руководстве.

Edit:

Для добавленного примера запрос может выглядеть следующим образом:

SELECT (x.value * 4 + y.value) / 5 AS result
FROM   ([get some neighbours by distance] order by value limit 1) x
      ,([get some neighbours by distance] order by value offset 1 limit 1) y;

Я добавил недостающее (), чтобы получить ожидаемый результат!

Или мой последний удар:

SELECT y.x, y.x[1], (y.x[1] * 4 + y.x[2]) / 5 AS result
FROM  (
    SELECT ARRAY(
        SELECT value FROM tbl WHERE [some condition] ORDER BY value LIMIT 2
        ) x
    ) y

Было бы намного проще , если бы вы предоставили полный запрос и определения таблиц.

...