PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Função Postgres retornando uma linha como valor JSON


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