小编典典

单个查询中两个值之间的插值

algorithm

我想通过在两个最近的邻居之间插值来计算一个值。我有一个子查询,该查询以两列和两个元素的形式返回邻居的值及其相对距离。

比方说:

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

如何通过线性插值计算点的值?是否可以在单个查询中执行此操作?

示例: 我的点的邻居A在距离1处的值为10,邻居B在距离4处的值为20。该函数应10 * 4 + 20 * 1 / 5 = 12为我的点返回一个值。

我尝试了明显的方法

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

这将失败,因为您无法使用group子句中的group子句。也不能使用另一个子查询返回总和,因为那样我就不能同时转发各个值。


阅读 259

收藏
2020-07-28

共1个答案

小编典典

这是一个丑陋的黑客行为(基于滥用的CTE;)。关键是

value1 * distance2 + value2 * distance1

可以通过除以distance1 * distance2来重写为

value1/distance1 + value2/distance2

因此,产品(或部门)可以留在其行内。求和后,乘以(distance1 * distance2)可将结果重新缩放为所需的输出。读者可以将对两个以上邻居的归纳作为练习。

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

更新:这 有点 干净…关联仍未处理(为此需要外部函数中的窗口函数或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
    ;
2020-07-28