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
パラメータ値を適切に使用するにはどうすればよいですか?
あなたはいくつかのことを混乱させています。 valuesをEXECUTE
に渡すには、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
句を使用してvaluesをUSING
に渡します。関連:
未使用の変数を削除しました と未使用のパラメータ id int;
。それに応じて序数参照(organizationId text
$n
)を調整。
EXECUTE
内の$n
表記(USING
句の項目を参照)を関数本体の$n
表記(関数パラメーターを参照)と混同しないでください。関連:
WHERE
句を連結するようにロジックを簡略化しました。コーナーケースエラーがありました。最初の割り当てで空の文字列が発生した場合は、AND
から始めます-構文エラー。
命名の競合を回避する命名規則を採用してください。パラメータ名は、関数のすべてのステートメントで表示されます(ただし、EXECUTE
!の内部は表示されません)。列名と競合する変数名を使用しないでください。一般的な規則は、パラメータと変数名の前に_
を付けることです。
特に動的SQLを使用する場合は、Postgresで大文字と小文字が混在する識別子を避けることをお勧めします。
SOの関連回答: