sql - 确定框之间的距离

标签 sql database algorithm postgresql postgis

这个问题让我头疼了一阵子。我有一个 PostgreSQL 8.4 数据库,它只包含一个包含超过 4.000.000 条记录的表。该表的结构如下:

CREATE TABLE metadata (
  id serial NOT NULL,
  "value" text NOT NULL DEFAULT ''::text,
  segment_box box NOT NULL DEFAULT box(point(((-9223372036854775808)::bigint)::double precision, (1)::double precision), point((9223372036854775807::bigint)::double precision, ((-1))::double precision)),
  CONSTRAINT metadata_pk PRIMARY KEY (id)
)

CREATE INDEX metadata_segment_box_ix
  ON metadata
  USING gist
  (segment_box);

CREATE INDEX metadata_tag_value_ix
  ON metadata
  USING btree
  (value);

该表包含段(按时间),表示为矩形框。这些段使用“值”列进行注释。

我想在数据库上执行的查询类型尝试查找包含在特定窗口中的具有指定值的所有段。成功实现此目的的查询是:

SELECT * FROM (SELECT * FROM metadata WHERE value='X') a, 
(SELECT * FROM metadata WHERE AND value='Y') b 
WHERE a.segment_box <-> b.segment_box <= 3000

但是,您可能已经注意到,数据库无法有效地执行此查询。子查询 a 和 b 的笛卡尔积变得非常大。有没有办法更有效地执行这些查询?我可以想象某种滑动窗口方法可以解决问题。可能类似于以下内容:

SELECT *, rank() OVER (
PARTITION BY "value" ORDER BY (segment_box[1])[0], (segment_box[0])[0]
) FROM metadata WHERE value='X' OR value='Y'

更新: 发布此问题后我尝试的其中一件事是在 Postgres 中创建自定义函数。我试过:

CREATE OR REPLACE FUNCTION within_window(size bigint DEFAULT 0)
  RETURNS setof metadata AS
$BODY$DECLARE
  segment RECORD;
  neighbour RECORD;
  newwindow box;
BEGIN
  FOR segment IN (
    SELECT * FROM metadata WHERE value='X' OR value='Y' 
      ORDER BY (segment_box[1])[0], (segment_box[0])[0]
  ) LOOP
    newwindow := box(segment.segment_box[0], 
      point((((segment.segment_box[1])[0]) + size), (segment.segment_box[1])[1]));
    FOR neighbour IN (
      SELECT DISTINCT ON (metadata_id) * FROM metadata WHERE value='X' OR value='Y') 
        AND segment_box &< newwindow
        AND segment_box &> newwindow 
    ) LOOP
      RETURN NEXT neighbour;
    END LOOP;
  END LOOP;
END;$BODY$
  LANGUAGE plpgsql;

但是,由于必须多次执行子查询,此功能与我上面描述的基本解决方案一样慢。对此还有其他想法吗??

最佳答案

我自己用一种扫描线算法解决了这个问题。只执行一次查询。我使用游标来回扫过查询的结果集。生成的算法的工作原理如下:

CREATE OR REPLACE FUNCTION within_window(size bigint DEFAULT 0)
  RETURNS setof metadata AS
$BODY$DECLARE 
crsr SCROLL CURSOR FOR (SELECT * FROM metadata WHERE value='X' OR value='Y' ORDER BY (segment_box[1])[0], (segment_box[0])[0]);
rc RECORD;
rcc RECORD;
crsr_position int;
last_crsr int;
BEGIN
    OPEN crsr;
    crsr_position := 0;
    LOOP FETCH NEXT FROM crsr INTO rc;
        IF NOT FOUND THEN
            EXIT;
        END IF;
        last_crsr := crsr_position;
        LOOP FETCH NEXT FROM crsr INTO rcc;
            IF NOT FOUND THEN
                EXIT;
            ELSEIF 
                rcc.segment_box &< box(rc.segment_box[0], point((((rc.segment_box[1])[0]) + size), (rc.segment_box[1])[1])) AND
                rcc.segment_box &> box(rc.segment_box[0], point((((rc.segment_box[1])[0]) + size), (rc.segment_box[1])[1]))
            THEN
                RETURN NEXT rcc;
            ELSE 
                EXIT;
            END IF;
        END LOOP;
        crsr_position := last_crsr + 1;
        MOVE ABSOLUTE crsr_position FROM crsr;
    END LOOP;
    CLOSE crsr;
END;$BODY$
  LANGUAGE plpgsql;

使用这个函数查询只需要 476 毫秒而不是 6 分钟以上(在 4+ 百万行的数据库上)!

关于sql - 确定框之间的距离,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6845085/

相关文章:

mysql - 相关名称在内部查询中如何工作?

algorithm - 替换嵌套循环的设计模式

algorithm - 排课

python - 在整数数组中查找偏移序列

sql - 在集群数据库上运行 PostGIS 命令

mysql - MySQL统计某列模式出现的次数

sql - 优化MySQL中的三表连接

mysql - 优秀的开源 MariaDB 前端

database - 一组外键,其中除了一个外都是 NULL

php - mysqli连接并从数据库中选择错误