Vejo dois problemas principais:
1. Você não pode colocar um
UPDATE
em uma subconsulta de todo . Você pode resolver isso com uma modificação de dados CTE
como Patrick demonstra
, mas isso é mais caro e detalhado do que o necessário para o caso em questão.2. Você tem um conflito de nomenclatura potencialmente perigoso , que ainda não foi abordado.
Melhor consulta/função
Deixando de lado o wrapper da função SQL por enquanto (voltaremos a isso). Você pode usar um simples
UPDATE
com um RETURNING
cláusula:UPDATE tbl
SET value1 = 'something_new'
WHERE id = 123
RETURNING row_to_json(ROW(value1, value2));
O
RETURNING
cláusula permite expressões arbitrárias envolvendo colunas da linha atualizada. Isso é mais curto e mais barato do que um CTE modificador de dados. O problema restante:o construtor de linha
ROW(...)
não preserva os nomes das colunas (que é uma fraqueza conhecida), então você obtém chaves genéricas em seu valor JSON:row_to_json
{"f1":"something_new","f2":"what ever is in value2"}
No Postgres 9.3, você precisaria de uma outra função CTE para encapsular a primeira etapa ou uma conversão para um tipo de linha bem definido. Detalhes:
No Postgres 9.4 basta usar
json_build_object()
ou json_object()
:UPDATE tbl
SET value1 = 'something_new'
WHERE id = 123
RETURNING json_build_object('value1', value1, 'value2', value2);
Ou:
...
RETURNING json_object('{value1, value2}', ARRAY[value1, value2]);
Agora você obtém nomes de colunas originais ou o que você escolheu como nomes de chave:
row_to_json
{"value1":"something_new","value2":"what ever is in value2"}
É fácil envolver isso em uma função, o que nos leva ao seu segundo problema ...
Conflito de nomenclatura
Em sua função original, você usa nomes idênticos para parâmetros de função e nomes de coluna. Geralmente, essa é uma ideia muito ruim . Você precisaria entender intimamente qual identificador vem primeiro em qual escopo.
No caso em questão, o resultado é um completo absurdo:
Create Or Replace Function ExampleTable_Update (id bigint, value1 text) Returns
...
Update ExampleTable
Set Value1 = value1
Where id = id
Returning Value1, Value2;
...
$$ Language SQL;
Enquanto você espera que a segunda instância de
id
faria referência ao parâmetro da função, não. O nome da coluna vem primeiro no escopo de uma instrução SQL, a segunda instância faz referência à coluna. resultando em uma expressão que é sempre true
exceto para valores NULL em id
. Consequentemente, você atualizaria todas as linhas , o que pode levar à perda catastrófica de dados .O que é pior, você pode nem perceber isso mais tarde, porque a função SQL retornará um linha arbitrária conforme definido pelo RETURNING
cláusula da função (retorna um linha, não um conjunto de linhas). Neste caso específico, você teria "sorte", porque também tem
value1 = value1
, que sobrescreve a coluna com seu valor pré-existente, efetivamente fazendo .. nada de uma maneira muito cara (a menos que os gatilhos façam algo). Você pode ficar confuso ao obter uma linha arbitrária com um value1
inalterado como resultado. Então, não.
Evite potenciais conflitos de nomenclatura como este, a menos que você saiba exatamente o que está fazendo (o que obviamente não é o caso). Uma convenção que eu gosto é colocar um sublinhado para nomes de parâmetros e variáveis em funções, enquanto os nomes de colunas nunca começam com um sublinhado. Em muitos casos, você pode usar apenas referências posicionais para não ter ambiguidade:
$1
, $2
, ..., mas isso evita apenas metade da questão. Qualquer método é bom, desde que você evite conflitos de nomenclatura . Eu sugiro:CREATE OR REPLACE FUNCTION foo (_id bigint, _value1 text)
RETURNS json AS
$func$
UPDATE tbl
SET value1 = _value1
WHERE id = _id
RETURNING json_build_object('value1', value1, 'value2', value2);
$func$ LANGUAGE sql;
Observe também que isso retorna o valor real da coluna em
value1
após o UPDATE
, que pode ou não ser igual ao seu parâmetro de entrada _value1
. Pode haver regras de banco de dados ou gatilhos interferindo ...