PostgreSQL中int4range类型的快速最近邻匹配

PostgreSQL中int4range类型的快速最近邻匹配,postgresql,nearest-neighbor,Postgresql,Nearest Neighbor,我有两个表,范围是t1和t2,我试图为t1中的每个条目找到t2中最接近的匹配项 我对距离的定义如下: -- distance function CREATE OR REPLACE FUNCTION distance( pos1 int4range, pos2 int4range) RETURNS integer LANGUAGE 'sql' AS $BODY$SELECT CASE WHEN pos1 && pos2 THEN 0 WHEN pos

我有两个表,范围是t1和t2,我试图为t1中的每个条目找到t2中最接近的匹配项

我对距离的定义如下:

-- distance function
CREATE OR REPLACE FUNCTION distance(
    pos1 int4range,
    pos2 int4range)
    RETURNS integer
    LANGUAGE 'sql'
AS $BODY$SELECT CASE
WHEN pos1 && pos2 THEN 0
WHEN pos1 >> pos2 THEN lower(pos1) - upper(pos2)
WHEN pos1 << pos2 THEN lower(pos2) - upper(pos1)
ELSE NULL
END AS distance$BODY$;
SELECT setseed(0.20191109);

DROP TABLE IF EXISTS t1;
DROP TABLE IF EXISTS t2;

WITH
t1n AS
(
    SELECT generate_series (1,500) AS id1, floor(random()*1e7)::integer AS n 
)
SELECT id1, int4range(n, n + ceil(random()*10)::integer) AS range1
INTO t1
FROM t1n;

WITH
t2n AS
(
    SELECT generate_series (1,10000) AS id2, floor(random()*1e7)::integer AS n 
)
SELECT id2, int4range(n, n + ceil(random()*100)::integer) AS range2
INTO t2
FROM t2n;
我认为使用联接和标量子查询的两种简单解决方案都非常低效,因为它们1计算所有可能的距离,2为t1中的每个值聚合或排序整个t2:

以下是rextester链接:

我想知道是否有一个更有效的解决方案,我错过了

更新:我已经尝试在范围类型上使用GiST、SP GiST、B-tree/GiST索引,但它们似乎都不支持在int4range上使用距离操作符?至少在PostgreSQL 10.5中没有?有没有办法将我在本文顶部定义的距离函数与这些索引类型一起使用

但这些似乎都不支持在int4range上使用距离操作符

他们不仅不支持该运算符,而且该运算符甚至不存在于int4range开箱即用中。您可以在SQL中轻松创建这样一个运算符:

create operator <-> ( function = distance, leftarg=int4range, rightarg=int4range);

我不确定“[]”是否是端点的正确处理方式,但此查询确实给出了与方法1相同的结果。

谢谢,我修复了解决方案2的代码中存在错误。还要记住注释掉方法1,CTE的结果似乎不会传递到多个查询。以下是使用表而不是CTE的单独版本:。但是,由于作业耗时太长,如果您不注释解决方案1,解决方案2仍然不起作用。由于您询问的是索引,而CTE无法索引,您应该编辑该问题,以使用与实际表的第二个链接。谢谢,这确实不是很漂亮,但到目前为止似乎有效!将在更大的集合上进行测试。当你说连接一个操作符和一个索引是困难的时候,你的意思是说,它似乎是一个非常简单的距离函数?当然,我的C语言知识还很初级。还有,为什么在这里同时使用b树和GiST索引?为什么不是全部?使用gist range2在t2上创建索引;在t2范围2上创建索引;在t2 int4range-upperrange2,-lowerrange2,“[]”上创建索引;另外,我想我需要>>/我必须先做,然后才能知道到底有多难。我认为gist索引不能有效地支持所需的orderby,这就是为什么需要btree索引。虽然我认为可以通过在标量上限上建立索引来简化,而不是在边切换的int4range上。
create operator <-> ( function = distance, leftarg=int4range, rightarg=int4range);
create index on t2 using gist (range2 );
create index on t2 (range2 );
create index on t2 (int4range(-upper(range2),-lower(range2),'[]'));


select id1, distance(range1,range2) AS dist from t1 cross join lateral 
(
    select * from (
       (select * from t2 where t2.range2 && t1.range1 limit 1) union all 
       (select * from t2 where t2.range2 > t1.range1 order by t2.range2 limit 1) union all 
       (select * from t2 where int4range(-upper(range2),-lower(range2),'[]') > int4range(-upper(range1),-lower(range1),'[]') order by int4range(-upper(range2),-lower(range2),'[]') limit 1)
    ) foobar order by distance(range1,range2) limit 1
) foo;