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
Another way to check if a JSON key exists is with json_extract_path(). Here's an example use:
SELECT permit_id FROM building_permits WHERE
json_extract_path(containing_boundaries, 'neighborhood') IS NULL
This looks for a property in your JSON object ("containing_boundaries") called "neighborhood". If the property does not exist, it returns NULL.
Found a simpler solution that checks if the json key have a value.
IF (user_info->>'username') IS NOT NULL
THEN
RAISE NOTICE 'username';
UPDATE users SET username = user_info->>'username' WHERE id = sp_update_user.user_id;
END IF;
This checks if the key 'username' in the json have a volume other than NULL.
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);
The "json_object_keys" function can be a solution.
You can select the result of a query that uses this function into a declared variable and then check if your variable is null. Here is an example:
DECLARE
test character varying;
BEGIN
SELECT a INTO test FROM json_object_keys(user_info) a WHERE a = 'lastname';
IF (test IS NOT NULL) THEN
--SUCESSS
ELSE
--FAIL
END IF;
END;