I\'m trying to check if a key exists in a JSON sent as parameter in a PL/pgSQL function.
Here is the function.
CREATE FUNCTION sp_update_user(user_in
You already found that you can test the expression user_info->>'username'
for NULL. But your function is still very inefficient. And there are still ambiguities.
It is expensive to update a row repeatedly for multiple columns. Postgres writes a new row version for every update. Use a single UPDATE
if at all possible:
CREATE OR REPLACE FUNCTION sp_update_user(_user_id int, _user_info json)
RETURNS json AS
$func$
BEGIN
UPDATE users u
SET firstname = COALESCE(_user_info->>'firstname', u.firstname)
, lastname = COALESCE(_user_info->>'lastname' , u.lastname)
WHERE id = sp_update_user._user_id
AND ((_user_info->>'firstname') IS NOT NULL OR
(_user_info->>'lastname') IS NOT NULL);
IF FOUND THEN
RETURN '{"success":true}'::json;
ELSE
RETURN '{"success":false}'::json;
END IF;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT sp_update_user(123, '{"firstname": "jon", "lastname": "doe"}')
This is substantially faster for multiple columns, since only a single UPDATE
(at most) is executed. If the WHERE
clause doesn't evaluate to true
, no update happens at all and you get '{"success":false}'
as result.
If sometimes the values in the table are already what they are being changed to, another optimization is possible. Consider the last paragraph of this related answer:
The variable / parameter user_id
is missing in your original.
There is still a corner case ambiguity. If the element exists and is set to JSON null
, you also get an SQL NULL
as result. Consider:
SELECT ('{"b": null}'::json->>'b') IS NULL AS b_is_null
, ('{"c": 2}'::json->>'b') IS NULL AS b_missing;
Not sure why you use data type json
as return type, I just kept that. But if the function does not update, you cannot be sure why you get false
. There might be no row with the given id
, the key names 'firstname'
and 'lastname'
could be missing - or be null
...
There is a clean and simple solution in Postgres 9.4 with jsonb
with the ? "existence" operator - which can even use an index for bigger tables (not relevant in your function):
SELECT ('{"b": null}'::jsonb ? 'b') AS b_is_null
, ('{"c": 2}'::jsonb ? 'b') AS b_missing;
And the ?| and ?& variants to check for multiple keys at once.
So we can implement:
CREATE OR REPLACE FUNCTION sp_update_user(_user_id int, _user_info jsonb)
RETURNS jsonb AS
$func$
BEGIN
UPDATE users u
SET firstname = CASE WHEN _user_info ? 'firstname' THEN _user_info->>'firstname' ELSE u.firstname END
, lastname = CASE WHEN _user_info ? 'lastname' THEN _user_info->>'lastname' ELSE u.lastname END
WHERE id = sp_update_user._user_id
AND _user_info ?| '{firstname,lastname}';
IF FOUND THEN
RETURN '{"success":true}'::jsonb;
ELSE
RETURN '{"success":false}'::jsonb;
END IF;
END
$func$ LANGUAGE plpgsql;
These calls work as expected now:
SELECT sp_update_user(123, '{"firstname": null, "lastname": "doe1"}'::jsonb);
SELECT sp_update_user(123, '{"firstname": "doris"}'::jsonb);