仅当查询结果不为 NULL 时如何正确返回查询结果?

Oxf*_*ist 4 postgresql performance spatial plpgsql query-performance

我正在编写一个 PL/pgSQL 函数,它为我需要检查它是否返回某些内容的查询创建一个游标。

我正在做的是这样的:

  1. 运行查询
  2. 检查它是否返回一些东西。
  3. 如果没有,请将参数加倍并再次运行查询。
  4. 否则,返回查询中的所有行。

我发现,检查,如果该查询返回的东西用光标是最好的选择,因为它(加入5个表和大量列)一个很长的查询和SELECT ... INTO不正确的顺心,因为我必须创建一个TYPE自查询具有来自一个表的列和用于距离计算的列。

问题是我目前仅使用游标来检查查询是否在我打开和关闭它的循环中返回了某些内容。一旦查询返回某些内容,我就退出循环并返回查询。我可以马上说,这是我需要的丑陋的解决方法。也许有人可以帮助我解决这个问题。

这是一些显示我目前正在做的事情的代码。

CREATE FUNCTION store_distance(
    latitude double precision,
    longitude double precision,
    radius double precision,
    tries integer
)
RETURNS TABLE(
    store_id store.id%type,
    store_name store.name%type,
    distance double precision
)
AS
$$
DECLARE
    cur_stores CURSOR FOR
        SELECT
            store.id,
            store.name,
            get_distance(latitude, longitude, store.latitude, store.longitude) distance
        FROM
            store
        WHERE
            store.latitude BETWEEN (latitude - radius) AND (latitude + radius)
            AND store.longitude BETWEEN (longitude - radius) AND (longitude + radius)
        ORDER BY
            distance ASC;
    count int := 0;
    storerow RECORD;
BEGIN
    LOOP
        IF count = tries THEN
            EXIT;
        END IF;
        OPEN cur_stores;
        FETCH cur_stores INTO storerow;
        IF FOUND THEN
            EXIT;
        END IF;
        radius := radius * 2;
        count := count + 1;
        CLOSE cur_stores;
    END LOOP;
    RETURN QUERY
    SELECT
        store.id,
        store.name,
        get_distance(latitude, longitude, store.latitude, store.longitude) distance
    FROM
        store
    WHERE
        store.latitude BETWEEN (latitude - radius) AND (latitude + radius)
        AND store.longitude BETWEEN (longitude - radius) AND (longitude + radius)
    ORDER BY
        distance ASC;
END;
$$ LANGUAGE PLPGSQL;
Run Code Online (Sandbox Code Playgroud)

所以我的目的是给出坐标、半径和尝试次数,并尝试在该搜索框中查找商店。如果没有找到商店,我将半径加倍并重试,直到查询返回某些内容或达到尝试次数为止。
RETURN TABLE部分基本上是因为我想回到的距离,这样RETURN SETOF store是没有用的。

Erw*_*ter 5

我认为您根本不需要光标。要缩短代码,您可以只使用视图。为了提高性能,物化视图应该能让你走得更远。Postgres 9.3 具有内置功能,但您可以自己在旧版本中轻松实现它。

考虑这个简化的形式:

CREATE FUNCTION store_distance(_lat    double precision
                              ,_long   double precision
                              ,_radius double precision
                              ,_tries  integer)
RETURNS TABLE(
   store_id   store.id%type
  ,store_name store.name%type
  ,distance   double precision) AS
$func$
DECLARE
   _ct  int   := 0;
   _pos point := point(_lat, _long);
BEGIN
   LOOP
      EXIT WHEN _ct >= _tries
           OR   EXISTS (
            SELECT 1 FROM store s
            WHERE  point(s.latitude, s.longitude) <@ circle(_pos, _radius));

      _radius := _radius * 2;
      _ct     := _ct + 1;
   END LOOP;

   RETURN QUERY
   SELECT s.id, s.name
         ,get_distance(_lat, _long, s.latitude, s.longitude)
   FROM   store s
   WHERE  point(s.latitude, s.longitude) <@ circle(_pos, _radius);
   ORDER  BY 3;
END
$func$ LANGUAGE plpgsql STRICT;
Run Code Online (Sandbox Code Playgroud)

我使函数STRICT禁止 NULL 输入,这可能会导致无限循环。

请注意我如何使用带有"Contained" 运算符而不是box 的circles。人们会认为计算比使用 box 稍微贵一点,但是一旦您使用GiST 索引支持您的查询,这几乎不重要,例如:<@

CREATE INDEX store_point_gist_idx ON store
USING gist (point(latitude, longitude));
Run Code Online (Sandbox Code Playgroud)

您可能会考虑将 lat / lon 存储为 point开始,并将表达式上的索引替换为列上更简单的索引。无论哪种方式都可以,只需确保查询与索引匹配,以便使用它。大桌子的区别很大。

您可能我去年发布的SO 上的这个密切相关的答案感兴趣- 有更多的解释和链接。