web-dev-qa-db-ja.com

クエリ結果がNULLでない場合にのみ正しく返す方法は?

何かを返すかどうかを確認する必要があるクエリのカーソルを作成するPL/pgSQL関数を書いています。

私がやっていることはこれです:

  1. クエリを実行する
  2. それが何かを返すかどうかを確認します。
  3. そうでない場合は、パラメータを2倍にして、クエリを再実行してください。
  4. それ以外の場合は、クエリからすべての行を返します。

クエリが非常に長いクエリ(5つのテーブルと多数の列を結合する)とSELECT ... INTOクエリは1つのテーブルの列と距離計算用の列を含むため、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;

したがって、私の目的は、座標、半径、試行回数を指定し、その検索ボックス内で店舗を検索して見つけることです。ストアが見つからない場合は、半径を2倍にして、クエリによって何かが返されるか、試行回数に達するまで再試行します。
RETURN TABLE部分は基本的に距離を返したいのでRETURN SETOF storeは役に立たなかった。

4
Oxfist

ここにカーソルは必要ないと思います。コードを短くするには、ビューを使用するだけです。パフォーマンスを向上させるには、 マテリアライズドビュー を使用すると、さらに遠くに移動できます。 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;

NULL入力を許可しないように関数STRICTを作成しました。これにより、無限ループが発生する可能性があります。

"Contained"演算子<@circlesを使用する方法に注意してくださいboxesの代わりに 計算はボックスよりも少し高価であると想定しますが、次のようなGistインデックスを使用してクエリをサポートすると、ほとんど問題になりません。

CREATE INDEX store_point_Gist_idx ON store
USING Gist (point(latitude, longitude));

Lat/lonを point として保存し、のインデックスを、カラム。どちらの方法でも機能します。クエリがインデックスと一致することを確認して、使用されるようにします。大きなテーブルの大きな違い。

あなたはこれに興味があるかもしれません SOと密接に関連した回答 昨年投稿した-より多くの説明とリンクを付けて。

5