J'ai écrit une fonction PL/pgSQL dans PostgreSQL 9.5. Il compile bien mais quand je l'appelle depuis pgAdmin3 cela me donne une erreur. Il semble que la requête dynamique avec des colonnes à remplacer par les paramètres passés dans la fonction ne fonctionne pas.
Voici ma fonction:
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;
Il compile bien mais donne l'erreur ci-dessous lorsque je l'appelle en utilisant la commande ci-dessous:
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
Comment utiliser correctement les valeurs des paramètres?
Vous confondez deux ou trois choses. Pour passer valeurs à EXECUTE
, utilisez la clause USING
. Vous n'avez pas besoin de format()
ici.
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;
Faites pas concaténez les valeurs des paramètres dans les chaînes SQL. Très fastidieux, lent, sujet aux erreurs et ouvert à l'injection SQL. Passez plutôt values à EXECUTE
avec la clause USING
. En relation:
J'ai supprimé la variable inutilisée et le paramètre inutilisé id int;
. Référence ordinale adaptée (organizationId text
$n
) en conséquence.
Ne confondez pas le $n
notation dans EXECUTE
(reportez-vous aux éléments de la clause USING
) avec $n
notation dans le corps de la fonction (voir les paramètres de la fonction)! En relation:
Simplifiez votre logique pour concaténer la clause WHERE
. Il y avait des erreurs de casse de coin: si l'affectation initiale aboutissait à une chaîne vide, vous commenciez par AND
- une erreur de syntaxe.
Adoptez une convention de dénomination qui évite les conflits de dénomination. Les noms de paramètres sont visibles dans toutes les instructions de la fonction (mais pas dans EXECUTE
!). N'utilisez pas de noms de variables en conflit avec les noms de colonnes. Une convention courante consiste à ajouter les noms de paramètres et de variables avec _
.
Mon conseil est d'éviter les identifiants à casse mixte dans Postgres, en particulier lors de l'utilisation de SQL dynamique.
Réponse connexe sur SO: