sql >> Base de Datos >  >> RDS >> PostgreSQL

Función de Postgres que devuelve una fila como valor JSON

Veo dos problemas principales:
1. No puedes poner un UPDATE en una subconsulta en absoluto . Podría resolver eso con un modificación de datos CET como Patrick demuestra , pero eso es más costoso y detallado de lo necesario para el caso en cuestión.
2. Tienes un conflicto de nombres potencialmente peligroso , que aún no se ha abordado.

Mejor consulta/función

Dejando a un lado el envoltorio de la función SQL por el momento (volveremos a eso). Puedes usar un simple UPDATE con un RETURNING cláusula:

UPDATE tbl
SET    value1 = 'something_new'
WHERE  id = 123
RETURNING row_to_json(ROW(value1, value2));

El RETURNING La cláusula permite expresiones arbitrarias que involucran columnas de la fila actualizada. Eso es más corto y más barato que un CTE de modificación de datos.

El problema restante:el constructor de filas ROW(...) no conserva los nombres de las columnas (que es una debilidad conocida), por lo que obtiene claves genéricas en su valor JSON:

row_to_json
{"f1":"something_new","f2":"what ever is in value2"}

En Postgres 9.3, necesitaría una CTE, otra función para encapsular el primer paso o una conversión a un tipo de fila bien definido. Detalles:

En Postgres 9.4 simplemente use json_build_object() o json_object() :

UPDATE tbl
SET    value1 = 'something_new'
WHERE  id = 123
RETURNING json_build_object('value1', value1, 'value2', value2);

O:

...
RETURNING json_object('{value1, value2}', ARRAY[value1, value2]);

Ahora obtiene los nombres de las columnas originales o lo que elija como nombres clave:

row_to_json
{"value1":"something_new","value2":"what ever is in value2"}

Es fácil envolver esto en una función, lo que nos lleva a su segundo problema...

Conflicto de nombres

En su función original, usa nombres idénticos para los parámetros de la función y los nombres de las columnas. En general, esta es una muy mala idea. . Necesitaría comprender íntimamente qué identificador viene primero en qué ámbito.

En el caso que nos ocupa, el resultado es una completa tontería:

Create Or Replace Function ExampleTable_Update (id bigint, value1 text) Returns 
...
    Update ExampleTable
    Set Value1 = value1
    Where id = id
    Returning Value1, Value2;
...
$$ Language SQL;

Si bien parece esperar que la segunda instancia de id haría referencia al parámetro de función, no lo hace. El nombre de la columna viene primero dentro del alcance de una instrucción SQL, la segunda instancia hace referencia a la columna. resultando en una expresión que siempre es true excepto por valores NULL en id . En consecuencia, actualizaría todas las filas , lo que podría provocar una pérdida catastrófica de datos .Lo que es peor, es posible que ni siquiera se dé cuenta hasta más tarde, porque la función SQL devolverá uno fila arbitraria según lo definido por RETURNING cláusula de la función (devuelve uno fila, no un conjunto de filas).

En este caso particular, tendrías "suerte", porque también tienes value1 = value1 , que sobrescribe la columna con su valor preexistente, efectivamente haciendo... nada de una manera muy costosa (a menos que los disparadores hagan algo). Es posible que se sienta desconcertado al obtener una fila arbitraria con un value1 sin cambios como resultado.

Así que no lo hagas.

Evite posibles conflictos de nombres como este a menos que sepa exactamente lo que está haciendo (que obviamente no es el caso). Una convención que me gusta es anteponer un guión bajo para los nombres de parámetros y variables en las funciones, mientras que los nombres de las columnas nunca comienzan con un guión bajo. En muchos casos, puede usar referencias posicionales para no ser ambiguo:$1 , $2 , ..., pero eso elude solo la mitad del problema. Cualquier método es bueno siempre y cuando evites conflictos de nombres . Sugiero:

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;

También tenga en cuenta que esto devuelve el valor de columna real en value1 después de UPDATE , que puede o no ser el mismo que su parámetro de entrada _value1 . Podría haber reglas de la base de datos o activadores que interfieren...