web-dev-qa-db-ja.com

EXECUTEを使用して動的SQLで関数パラメーターを使用する方法

PostgreSQL 9.5でPL/pgSQL関数を記述しました。うまくコンパイルできますが、pgAdmin3から呼び出すとエラーになります。関数で渡されたパラメータで置き換えられる列を含む動的クエリが機能していないようです。

以下は私の機能です:

CREATE OR REPLACE FUNCTION insertRecordsForNotification(username text, state     text, district text, organizationId text, bloodGroup text, status text,   approveRejectStatus text, emailSubject text, emailBody text, notificationStatus text) RETURNS boolean AS $$
DECLARE
id int;
r moyadev.user%rowtype;
_where text :=
  concat_ws(' AND '
    , CASE WHEN state IS NOT NULL THEN 'state = $2' END
    , CASE WHEN district IS NOT NULL THEN 'district = $3' END
    , CASE WHEN bloodGroup IS NOT NULL THEN 'bloodGroup = $5' END
    , CASE WHEN status IS NOT NULL THEN 'status = $6' END
    , CASE WHEN approveRejectStatus IS NOT NULL THEN 'approve_reject_status  = $7' END);   
 _sql text := 'INSERT INTO moyadev.notification_email_details (id,     youth_enrollment_id, youth_email, email_subject, email_body, status, attempt,  sent_date, last_updated_by, last_updated) SELECT uuid_generate_v4(), id, email,  $8, $9, $10, null, null,$1, now() FROM moyadev.youth_enrollment';

 BEGIN

SELECT * into r FROM moyadev.user u where u.user_key=$1;


if (r.level='DISTRICT') then
_where := _where || ' AND ' || 'district=r.district' || ' AND ' ||      'state=r.state' || ' AND ' || 'fk_id=r.fk_id';
elseif (r.level='STATE') then
_where := _where || ' AND ' || 'state=r.state' || ' AND ' ||   'fk_id=r.fk_id';
 elseif (r.level='NATIONAL') then
 _where := _where || ' AND ' || 'fk_id=r.fk_id';
 elseif (r.level='UNIT') then
_ where := _where || ' AND ' || 'district=r.district' || ' AND ' ||    'state=r.state' || ' AND ' || 'fk_id=r.fk_id';
end if;

IF _where <> '' THEN
_sql := _sql || ' WHERE ' || _where;
EXECUTE format(_sql);
END IF;

raise notice 'sql: %', _sql;

RETURN 'TRUE';

END; 
$$ LANGUAGE PLPGSQL;

うまくコンパイルされますが、以下のコマンドを使用して呼び出すと、以下のエラーが発生します。

select insertRecordsForNotification('[email protected]',null,null,null,null,'ACTIVE','APPROVED','test email','test email','PENDING');
ERROR: there is no parameter $8
SQL state: 42P02
Context: PL/pgSQL function insertrecordsfornotification(text,text,text,text,text,text,text,text,text,text) line 39 at EXECUTE

パラメータ値を適切に使用するにはどうすればよいですか?

6
Pushpendra Pal

あなたはいくつかのことを混乱させています。 valuesEXECUTEに渡すには、USING句を使用します。ここではformat()は必要ありません。

CREATE OR REPLACE FUNCTION insert_records_for_notification(
        _username text
      , _state text
      , _district text
      , _bloodgroup text
      , _status text
      , _approverejectstatus text
      , _emailsubject text
      , _emailbody text
      , _notificationstatus text)
  RETURNS boolean AS
$func$
DECLARE
   r      moyadev.user%rowtype;
   _where text;
   _sql   text :=
     'INSERT INTO moyadev.notification_email_details (id, youth_enrollment_id, youth_email, email_subject, email_body, status, attempt,sent_date, last_updated_by, last_updated)
      SELECT uuid_generate_v4(), id, email, $7, $8, $9, null, null,$1, now()
      FROM   moyadev.youth_enrollment';
BEGIN
   SELECT * INTO r FROM moyadev.user u WHERE u.user_key = _username;

   _where := concat_ws(' AND '
       , CASE WHEN state               IS NOT NULL THEN 'state = $2'                  END
       , CASE WHEN district            IS NOT NULL THEN 'district = $3'               END
       , CASE WHEN bloodGroup          IS NOT NULL THEN 'bloodgroup = $4'             END
       , CASE WHEN status              IS NOT NULL THEN 'status = $5'                 END
       , CASE WHEN approveRejectStatus IS NOT NULL THEN 'approve_reject_status  = $6' END
       , CASE r.level
            WHEN 'DISTRICT' THEN 'district = $10 AND state = $11 AND fk_id = $12'
            WHEN 'UNIT'     THEN 'district = $10 AND state = $11 AND fk_id = $12'
            WHEN 'STATE'    THEN 'state = $11 AND fk_id = $12'
            WHEN 'NATIONAL' THEN 'fk_id = $12'
         END);

   IF _where <> '' THEN
      _sql := _sql || ' WHERE ' || _where;

      EXECUTE _sql
      USING   $1, $2, $3, $4, $5, $6, $7, $8, $9, r.district, r.state, r.fk_id;
   END IF;

   RAISE NOTICE 'sql: %', _sql;

   RETURN true;  -- boolean!
END
$func$  LANGUAGE plpgsql;

主なポイント

  • notパラメータ値をSQL文字列に連結します。非常に退屈で時間がかかり、エラーが発生しやすく、SQLインジェクションが発生しやすい。代わりに、EXECUTE句を使用してvaluesUSINGに渡します。関連:

  • 未使用の変数を削除しました id int; と未使用のパラメータ organizationId text。それに応じて序数参照($n)を調整。

  • EXECUTE内の$n表記(USING句の項目を参照)を関数本体の$n表記(関数パラメーターを参照)と混同しないでください。関連:

  • WHERE句を連結するようにロジックを簡略化しました。コーナーケースエラーがありました。最初の割り当てで空の文字列が発生した場合は、ANDから始めます-構文エラー。

  • 命名の競合を回避する命名規則を採用してください。パラメータ名は、関数のすべてのステートメントで表示されます(ただし、EXECUTE!の内部は表示されません)。列名と競合する変数名を使用しないでください。一般的な規則は、パラメータと変数名の前に_を付けることです。

  • 特に動的SQLを使用する場合は、Postgresで大文字と小文字が混在する識別子を避けることをお勧めします。

SOの関連回答:

11