私は他の回答のアドバイスのいくつかに同意しません。これは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');
すべての関数パラメータにはデフォルト値があるため、 position を使用できます 表記、名前付き 表記または混合 関数呼び出しで選択した表記。参照:
- 可変数の入力パラメーターを持つ関数
動的SQLの基本に関する詳細説明:
- PL / pgSQL関数をリファクタリングして、さまざまなSELECTクエリの出力を返します
concat()
関数は、文字列を作成するのに役立ちます。 Postgres9.1で導入されました。
ELSE
CASE
のブランチ ステートメントのデフォルトはNULL
存在しない場合。コードを簡素化します。
USING
EXECUTE
の句 値が値として渡されるため、SQLインジェクションが不可能になります プリペアドステートメントとまったく同じように、パラメータ値を直接使用できます。
NULL
ここでは、値はパラメーターを無視するために使用されます。実際には検索には使用されません。
SELECT
を括弧で囲む必要はありません 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 JOIN
のすべてのインスタンスを置き換えます JOIN
を使用 。
db<>ここでフィドル -簡単なデモ すべてのバリアント用。
古いsqlfiddle
傍白
-
name
は使用しないでください およびid
列名として。それらは説明的ではなく、(a lot
するように)たくさんのテーブルを結合するとき リレーショナルデータベースの場合)、すべてname
という名前の列がいくつか表示されます。 またはid
、および混乱を並べ替えるためにエイリアスを添付する必要があります。 -
少なくとも一般の質問をするときは、SQLを適切にフォーマットしてください。しかし、あなた自身のために、個人的にもそれを行ってください。