9

私は Postgres 関数を持っています:

create function myfunction(integer, text, text, text, text, text, text) RETURNS 
table(id int, match text, score int, nr int, nr_extra character varying, info character varying, postcode character varying,
street character varying, place character varying, country character varying, the_geom geometry)
AS $$
BEGIN

return query (select a.id, 'address' as match, 1 as score, a.ad_nr, a.ad_nr_extra,a.ad_info,a.ad_postcode, s.name as street, p.name place , c.name country, a.wkb_geometry as wkb_geometry from "Addresses" a 
    left join "Streets" s on a.street_id = s.id 
        left join "Places" p on s.place_id = p.id 
            left join "Countries" c on p.country_id = c.id 
            where c.name = $7 
                and p.name = $6
                    and s.name = $5
                    and a.ad_nr = $1 
                    and a.ad_nr_extra = $2
                    and a.ad_info = $3
                    and ad_postcode = $4);
END;
$$
LANGUAGE plpgsql;

入力された変数の 1 つ以上が NULL の場合、この関数は正しい結果を返すことad_postcode = NULLができません。失敗するためです。

クエリ内で NULL をテストするにはどうすればよいですか?

4

4 に答える 4

25

他の回答のアドバイスのいくつかに同意しません。これは PL/pgSQL で実行でき、クライアント アプリケーションでクエリを組み立てるよりもはるかに優れていると思います。これはより高速でクリーンであり、アプリは必要最小限のリクエストのみをネットワーク経由で送信します。SQL ステートメントはデータベース内に保存されるため、保守が容易になります。すべてのビジネス ロジックをクライアント アプリケーションに収集する場合を除き、これは一般的なアーキテクチャに依存します。

動的 SQL を使用した PL/pgSQL 関数

CREATE OR REPLACE FUNCTION func(
      _ad_nr       int  = NULL
    , _ad_nr_extra text = NULL
    , _ad_info     text = NULL
    , _ad_postcode text = NULL
    , _sname       text = NULL
    , _pname       text = NULL
    , _cname       text = NULL)
  RETURNS TABLE(id int, match text, score int, nr int, nr_extra text
              , info text, postcode text, street text, place text
              , country text, the_geom geometry)
  LANGUAGE plpgsql AS
$func$
BEGIN
   -- RAISE NOTICE '%', -- for debugging
   RETURN QUERY EXECUTE concat(
   $$SELECT a.id, 'address'::text, 1 AS score, a.ad_nr, a.ad_nr_extra
        , a.ad_info, a.ad_postcode$$

   , CASE WHEN (_sname, _pname, _cname) IS NULL THEN ', NULL::text' ELSE ', s.name' END  -- street
   , CASE WHEN (_pname, _cname) IS NULL         THEN ', NULL::text' ELSE ', p.name' END  -- place
   , CASE WHEN _cname IS NULL                   THEN ', NULL::text' ELSE ', c.name' END  -- country
   , ', a.wkb_geometry'

   , concat_ws('
   JOIN   '
   , '
   FROM   "Addresses" a'
   , CASE WHEN NOT (_sname, _pname, _cname) IS NULL THEN '"Streets"   s ON s.id = a.street_id' END
   , CASE WHEN NOT (_pname, _cname) IS NULL         THEN '"Places"    p ON p.id = s.place_id' END
   , CASE WHEN _cname IS NOT NULL                   THEN '"Countries" c ON c.id = p.country_id' END
   )

   , concat_ws('
   AND    '
      , '
   WHERE  TRUE'
      , CASE WHEN $1 IS NOT NULL THEN 'a.ad_nr = $1' END
      , CASE WHEN $2 IS NOT NULL THEN 'a.ad_nr_extra = $2' END
      , CASE WHEN $3 IS NOT NULL THEN 'a.ad_info = $3' END
      , CASE WHEN $4 IS NOT NULL THEN 'a.ad_postcode = $4' END
      , CASE WHEN $5 IS NOT NULL THEN 's.name = $5' END
      , CASE WHEN $6 IS NOT NULL THEN 'p.name = $6' END
      , CASE WHEN $7 IS NOT NULL THEN 'c.name = $7' END
   )
   )
   USING $1, $2, $3, $4, $5, $6, $7;
END
$func$;

電話:

SELECT * FROM func(1, '_ad_nr_extra', '_ad_info', '_ad_postcode', '_sname');

SELECT * FROM func(1, _pname := 'foo');

すべての関数パラメーターにはデフォルト値があるため、関数呼び出しでは、位置表記法、名前付き表記法、または混合表記法を選択して使用できます。見る:

動的 SQL の基本に関する詳細な説明:

このconcat()関数は、文字列を構築するのに役立ちます。Postgres 9.1 で導入されました。

ステートメントのELSE分岐は、存在しない場合にデフォルトになります。コードを簡素化します。CASENULL

USINGfor 句は、EXECUTE値が値として渡されるため、SQL インジェクションを不可能にし、準備されたステートメントとまったく同じように、パラメーターを直接使用できるようにします。

NULLここではパラメータを無視するために値が使用されます。それらは実際には検索には使用されません。

SELECTwith を括弧で囲む必要はありませんRETURN QUERY

単純な SQL 関数

単純な SQL 関数を使用して実行し、動的 ​​SQL を回避できます。場合によっては、これがより高速になることがあります。しかし、この場合は期待できません。通常、不要な結合や述語を使用せずにクエリを計画すると、最良の結果が得られます。このような単純なクエリの計画コストはほとんど無視できます。

CREATE OR REPLACE FUNCTION func_sql(
     _ad_nr       int  = NULL
   , _ad_nr_extra text = NULL
   , _ad_info     text = NULL
   , _ad_postcode text = NULL
   , _sname       text = NULL
   , _pname       text = NULL
   , _cname       text = NULL)
  RETURNS TABLE(id int, match text, score int, nr int, nr_extra text
              , info text, postcode text, street text, place text
              , country text, the_geom geometry)
  LANGUAGE sql AS 
$func$
SELECT a.id, 'address' AS match, 1 AS score, a.ad_nr, a.ad_nr_extra
     , a.ad_info, a.ad_postcode
     , s.name AS street, p.name AS place
     , c.name AS country, a.wkb_geometry
FROM   "Addresses"      a
LEFT   JOIN "Streets"   s ON s.id = a.street_id
LEFT   JOIN "Places"    p ON p.id = s.place_id
LEFT   JOIN "Countries" c ON c.id = p.country_id
WHERE ($1 IS NULL OR a.ad_nr = $1)
AND   ($2 IS NULL OR a.ad_nr_extra = $2)
AND   ($3 IS NULL OR a.ad_info = $3)
AND   ($4 IS NULL OR a.ad_postcode = $4)
AND   ($5 IS NULL OR s.name = $5)
AND   ($6 IS NULL OR p.name = $6)
AND   ($7 IS NULL OR c.name = $7)
$func$;

同一の呼び出し。

を持つパラメータを効果的に無視するにはNULL:

($1 IS NULL OR a.ad_nr = $1)

パラメータとして NULL 値を実際に使用するには、代わりに次の構文を使用します。

($1 IS NULL AND a.ad_nr IS NULL OR a.ad_nr = $1)  -- AND binds before OR

これにより、インデックスを使用することもできます。当面の場合、 のすべてのインスタンスをに
置き換えます。LEFT JOINJOIN

db<>fiddle here -すべてのバリアントの簡単なデモがあります。
古いsqlfiddle

アシデス

  • nameandidを列名として使用しないでください。それらは説明的ではなく、一連のテーブルを結合すると (a lotリレーショナル データベースで行うように)、すべてがnameまたはという名前の複数の列idになり、混乱をソートするためにエイリアスを添付する必要があります。

  • 少なくとも公開質問をするときは、SQL を適切にフォーマットしてください。しかし、あなた自身の利益のために、個人的にもそうしてください。

于 2013-06-28T01:12:10.017 に答える