Check if key exists in a JSON with PL/pgSQL?

前端 未结 4 1495
时光说笑
时光说笑 2021-01-12 22:18

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         


        
4条回答
  •  野的像风
    2021-01-12 22:42

    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.

    Better solution in Postgres 9.3

    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:

      • How do I (or can I) SELECT DISTINCT on multiple columns?
    • 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 ...


    Superior solution in Postgres 9.4

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

提交回复
热议问题