使用 PL/pgSQL 检查密钥是不是存在于 JSON 中?

Posted

技术标签:

【中文标题】使用 PL/pgSQL 检查密钥是不是存在于 JSON 中?【英文标题】:Check if key exists in a JSON with PL/pgSQL?使用 PL/pgSQL 检查密钥是否存在于 JSON 中? 【发布时间】:2015-06-05 12:19:10 【问题描述】:

我正在尝试检查在 PL/pgSQL 函数中作为参数发送的 JSON 中是否存在密钥。

这里是函数。

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-clause,但是即使json键不存在,IF FOUND子句中的语句也会被执行。

我尝试PERFORM user_info->>'firstname' INTO myvar; 来检查变量内容,但它触发了错误ERROR: query "SELECT user_info->>'firstname' INTO myvar" is not a SELECT

如何使用 PL/pgSQL 检查 JSON 中是否存在 json 键?

【问题讨论】:

【参考方案1】:

您已经发现可以测试表达式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'

如果有时表中的值已经是它们要更改的值,则可以进行另一种优化。考虑这个相关答案的最后一段:

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 作为返回类型,我只是保留了它。但是如果函数没有更新,你就不能确定为什么会得到false。给定的id 可能没有行,键名'firstname''lastname' 可能丢失 - 或者是null ...

Postgres 9.4 中的卓越解决方案

在 Postgres 9.4 中有一个 clean 和简单的解决方案,带有 jsonb? "existence" operator - 甚至可以使用索引对于更大的表格(与您的功能无关):

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 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;

这些调用现在按预期工作:

SELECT sp_update_user(123, '"firstname": null, "lastname": "doe1"'::jsonb);
SELECT sp_update_user(123, '"firstname": "doris"'::jsonb);

【讨论】:

非常感谢您的回答,它比我以前发现的任何东西都要好得多!我正在认真考虑更新 Postgres 9.4... @DeadEye:如果你必须经常处理 JSON,那是个好主意。【参考方案2】:

“json_object_keys”函数可以作为解决方案。

您可以将使用此函数的查询结果选择到声明的变量中,然后检查您的变量是否为空。这是一个例子:

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;

【讨论】:

这个解决方案非常好!谢谢!【参考方案3】:

找到了一个更简单的解决方案来检查 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 中的键“用户名”是否具有 NULL 以外的卷。

【讨论】:

【参考方案4】:

另一种检查 JSON 密钥是否存在的方法是使用 json_extract_path()。这是一个使用示例:

SELECT permit_id FROM building_permits WHERE
json_extract_path(containing_boundaries, 'neighborhood') IS NULL

这会在您的 JSON 对象(“包含边界”)中查找名为“neighborhood”的属性。如果该属性不存在,则返回 NULL。

【讨论】:

以上是关于使用 PL/pgSQL 检查密钥是不是存在于 JSON 中?的主要内容,如果未能解决你的问题,请参考以下文章

PostgreSQL vs Oracle:PL/pgSQL 的“编译时”检查

如何检查密钥是不是存在于深层 Perl 哈希中?

检查密钥是不是存在于 JSON 中,然后插入到页面中

PL/pgSQL 重启生成序列

检查 Laravel 5.1 中是不是存在会话密钥?

PL/PGSQL触发器如何检测UPDATE上的SQL语句中是不是提供了列?