使用 PL/pgSQL 检查 JSON 中是否存在密钥?
Check if key exists in a JSON with PL/pgSQL?
我正在尝试检查 JSON 中是否存在密钥,作为 PL/pgSQL 函数中的参数发送。
这是函数。
CREATE FUNCTION sp_update_user(user_info json) RETURNS json
LANGUAGE plpgsql
AS $$
BEGIN
PERFORM user_info->>'lastname';
IF FOUND
THEN
UPDATE users SET lastname = user_info->>'lastname' WHERE id = sp_update_user.user_id;
END IF;
PERFORM user_info->>'firstname';
IF FOUND
THEN
UPDATE users SET firstname = user_info->>'firstname' WHERE id = sp_update_user.user_id;
END IF;
RETURN row_to_json(row) FROM (SELECT true AS success) row;
END;$$;
我尝试使用 PERFORM
子句,但即使 json 键不存在,也会执行 IF FOUND
子句中的语句。
我尝试 PERFORM user_info->>'firstname' INTO myvar;
来检查变量内容,但它触发了错误 ERROR: query "SELECT user_info->>'firstname' INTO myvar" is not a SELECT
。
如何检查 json 密钥是否存在于 JSON 和 PL/pgSQL 中?
"json_object_keys"函数可以解决。
您可以select将使用此函数的查询结果放入声明的变量中,然后检查您的变量是否为空。这是一个例子:
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;
找到一个更简单的解决方案来检查 json 键是否有值。
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;
这会检查 json 中的键 'username' 是否有非 NULL 的卷。
您已经发现可以测试表达式 user_info->>'username'
是否为 NULL。但是你的函数仍然效率很低。而且还有歧义.
Postgres 9.3 中更好的解决方案
为多列重复更新一行是昂贵的。 Postgres 为每次更新写入一个新的行版本。如果可能的话,使用单UPDATE
:
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;
致电:
SELECT sp_update_user(123, '{"firstname": "jon", "lastname": "doe"}')
这对于多列来说要快得多,因为只执行一个 UPDATE
(最多)。如果 WHERE
子句的计算结果不为 true
,则根本不会发生任何更新,结果是 '{"success":false}'
。
如果有时 table 中的值已经是它们正在更改的值,则可以进行另一种优化。考虑这个相关答案的最后一段:
- How do I (or can I) SELECT DISTINCT on multiple columns?
您的原件中缺少变量/参数 user_id
。
还有一个极端情况歧义。如果该元素存在并设置为 JSON null
,您还会得到 SQL NULL
作为结果。考虑:
SELECT ('{"b": null}'::json->>'b') IS NULL AS b_is_null
, ('{"c": 2}'::json->>'b') IS NULL AS b_missing;
不确定为什么将数据类型 json
用作 return 类型,我只是保留了它。但是如果函数没有更新,你就不能确定为什么会得到false
。可能没有包含给定 id
的行,键名 'firstname'
和 'lastname'
可能丢失 - 或者是 null
...
Postgres 9.4 中的高级解决方案
在 Postgres 9.4 中有一个 干净 和简单的解决方案 jsonb
与 ?
"existence" operator - 它甚至可以使用更大的 tables 的索引(与您的功能无关):
SELECT ('{"b": null}'::jsonb ? 'b') AS b_is_null
, ('{"c": 2}'::jsonb ? 'b') AS b_missing;
和 ?|
and ?&
variants 一次检查多个密钥。
所以我们可以实现:
CREATE OR REPLACE FUNCTION sp_update_user(_user_id int, _user_info <b>jsonb</b>)
RETURNS <b>jsonb</b> AS
$func$
BEGIN
UPDATE users u
SET firstname = <b>CASE WHEN _user_info ? 'firstname' THEN _user_info->>'firstname' ELSE u.firstname END</b>
, lastname = <b>CASE WHEN _user_info ? 'lastname' THEN _user_info->>'lastname' ELSE u.lastname END</b>
WHERE id = sp_update_user._user_id
AND <b>_user_info ?| '{firstname,lastname}'</b>;
IF FOUND THEN
RETURN '{"success":true}'::jsonb;
ELSE
RETURN '{"success":false}'::jsonb;
END IF;
END
$func$ LANGUAGE plpgsql;
这些调用现在按预期工作:
SELECT sp_update_user(123, '{"firstname": null, "lastname": "doe1"}'::jsonb);
SELECT sp_update_user(123, '{"firstname": "doris"}'::jsonb);
另一种检查 JSON 键是否存在的方法是使用 json_extract_path()
。这是一个使用示例:
SELECT permit_id FROM building_permits WHERE
json_extract_path(containing_boundaries, 'neighborhood') IS NULL
这会在名为 "neighborhood" 的 JSON 对象 ("containing_boundaries") 中查找 属性。如果 属性 不存在,则 returns NULL。
我正在尝试检查 JSON 中是否存在密钥,作为 PL/pgSQL 函数中的参数发送。
这是函数。
CREATE FUNCTION sp_update_user(user_info json) RETURNS json
LANGUAGE plpgsql
AS $$
BEGIN
PERFORM user_info->>'lastname';
IF FOUND
THEN
UPDATE users SET lastname = user_info->>'lastname' WHERE id = sp_update_user.user_id;
END IF;
PERFORM user_info->>'firstname';
IF FOUND
THEN
UPDATE users SET firstname = user_info->>'firstname' WHERE id = sp_update_user.user_id;
END IF;
RETURN row_to_json(row) FROM (SELECT true AS success) row;
END;$$;
我尝试使用 PERFORM
子句,但即使 json 键不存在,也会执行 IF FOUND
子句中的语句。
我尝试 PERFORM user_info->>'firstname' INTO myvar;
来检查变量内容,但它触发了错误 ERROR: query "SELECT user_info->>'firstname' INTO myvar" is not a SELECT
。
如何检查 json 密钥是否存在于 JSON 和 PL/pgSQL 中?
"json_object_keys"函数可以解决。
您可以select将使用此函数的查询结果放入声明的变量中,然后检查您的变量是否为空。这是一个例子:
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;
找到一个更简单的解决方案来检查 json 键是否有值。
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;
这会检查 json 中的键 'username' 是否有非 NULL 的卷。
您已经发现可以测试表达式 user_info->>'username'
是否为 NULL。但是你的函数仍然效率很低。而且还有歧义.
Postgres 9.3 中更好的解决方案
为多列重复更新一行是昂贵的。 Postgres 为每次更新写入一个新的行版本。如果可能的话,使用单UPDATE
:
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;
致电:
SELECT sp_update_user(123, '{"firstname": "jon", "lastname": "doe"}')
这对于多列来说要快得多,因为只执行一个
UPDATE
(最多)。如果WHERE
子句的计算结果不为true
,则根本不会发生任何更新,结果是'{"success":false}'
。如果有时 table 中的值已经是它们正在更改的值,则可以进行另一种优化。考虑这个相关答案的最后一段:
- How do I (or can I) SELECT DISTINCT on multiple columns?
您的原件中缺少变量/参数
user_id
。还有一个极端情况歧义。如果该元素存在并设置为 JSON
null
,您还会得到 SQLNULL
作为结果。考虑:SELECT ('{"b": null}'::json->>'b') IS NULL AS b_is_null , ('{"c": 2}'::json->>'b') IS NULL AS b_missing;
不确定为什么将数据类型
json
用作 return 类型,我只是保留了它。但是如果函数没有更新,你就不能确定为什么会得到false
。可能没有包含给定id
的行,键名'firstname'
和'lastname'
可能丢失 - 或者是null
...
Postgres 9.4 中的高级解决方案
在 Postgres 9.4 中有一个 干净 和简单的解决方案 jsonb
与 ?
"existence" operator - 它甚至可以使用更大的 tables 的索引(与您的功能无关):
SELECT ('{"b": null}'::jsonb ? 'b') AS b_is_null
, ('{"c": 2}'::jsonb ? 'b') AS b_missing;
和 ?|
and ?&
variants 一次检查多个密钥。
所以我们可以实现:
CREATE OR REPLACE FUNCTION sp_update_user(_user_id int, _user_info <b>jsonb</b>)
RETURNS <b>jsonb</b> AS
$func$
BEGIN
UPDATE users u
SET firstname = <b>CASE WHEN _user_info ? 'firstname' THEN _user_info->>'firstname' ELSE u.firstname END</b>
, lastname = <b>CASE WHEN _user_info ? 'lastname' THEN _user_info->>'lastname' ELSE u.lastname END</b>
WHERE id = sp_update_user._user_id
AND <b>_user_info ?| '{firstname,lastname}'</b>;
IF FOUND THEN
RETURN '{"success":true}'::jsonb;
ELSE
RETURN '{"success":false}'::jsonb;
END IF;
END
$func$ LANGUAGE plpgsql;
这些调用现在按预期工作:
SELECT sp_update_user(123, '{"firstname": null, "lastname": "doe1"}'::jsonb);
SELECT sp_update_user(123, '{"firstname": "doris"}'::jsonb);
另一种检查 JSON 键是否存在的方法是使用 json_extract_path()
。这是一个使用示例:
SELECT permit_id FROM building_permits WHERE
json_extract_path(containing_boundaries, 'neighborhood') IS NULL
这会在名为 "neighborhood" 的 JSON 对象 ("containing_boundaries") 中查找 属性。如果 属性 不存在,则 returns NULL。