Você já descobriu que pode testar a expressão
user_info->>'username'
para NULO. Mas sua função ainda é muito ineficiente . E ainda há ambiguidades . Melhor solução no Postgres 9.3
É caro atualizar uma linha repetidamente para várias colunas. O Postgres escreve uma nova versão de linha para cada atualização. Use um único
UPDATE
se tudo for possível: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;
Ligar:
SELECT sp_update_user(123, '{"firstname": "jon", "lastname": "doe"}')
-
Isso é substancialmente mais rápido para várias colunas, pois apenas um únicoUPDATE
(no máximo) é executado. Se oWHERE
cláusula não é avaliada comotrue
, nenhuma atualização acontece e você obtém'{"success":false}'
como resultado.
-
Se algumas vezes os valores na tabela já forem os valores para os quais estão sendo alterados, outra otimização é possível. Considere o último parágrafo desta resposta relacionada:
-
A variável / parâmetrouser_id
está faltando no seu original.
-
Ainda há um caso de canto ambiguidade . Se o elemento existir e estiver definido como JSONnull
, você também obtém um SQLNULL
como resultado. Considerar:
SELECT ('{"b": null}'::json->>'b') IS NULL AS b_is_null , ('{"c": 2}'::json->>'b') IS NULL AS b_missing;
-
Não tenho certeza por que você usa o tipo de dadosjson
como tipo de retorno, eu apenas mantive isso. Mas se a função não for atualizada, você não pode ter certeza por que obtémfalse
. Pode não haver nenhuma linha com oid
fornecido , os nomes das chaves'firstname'
e'lastname'
pode estar faltando - ou sernull
...
Solução superior no Postgres 9.4
Há um limpo e solução simples no Postgres 9.4 com
jsonb
com o ?
operador "existência"
- que pode até usar um índice para tabelas maiores (não relevante em sua função):SELECT ('{"b": null}'::jsonb ? 'b') AS b_is_null
, ('{"c": 2}'::jsonb ? 'b') AS b_missing;
E o
?|
e ?&
variantes
para verificar várias chaves de uma só vez.Então podemos implementar:
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;
Essas chamadas funcionam como esperado agora:
SELECT sp_update_user(123, '{"firstname": null, "lastname": "doe1"}'::jsonb);
SELECT sp_update_user(123, '{"firstname": "doris"}'::jsonb);