sql >> データベース >  >> RDS >> PostgreSQL

さまざまなパラメーターを使用して関数のnullをテストします

    私は他の回答のアドバイスのいくつかに同意しません。これは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を適切にフォーマットしてください。しかし、あなた自身のために、個人的にもそれを行ってください。



    1. MariaDBで省略記号を使用してテキストを切り捨てる方法

    2. MySQLで2つのテーブルを比較する方法

    3. データベースからデータをプルしてListViewとして表示する方法

    4. Workbenchを使用してSSHトンネル経由でリモートMySQLサーバーに接続する