Jeg ser to store problemer:
1. Du kan ikke indsætte en OPDATERING ind i en underforespørgsel overhovedet
. Du kunne løse det med en datamodificering CTE
som Patrick demonstrerer
, men det er dyrere og mere omfattende end nødvendigt for den aktuelle sag.
2. Du har en potentielt farlig navnekonflikt , det er ikke blevet behandlet endnu.
Bedre forespørgsel/funktion
Lader SQL-funktionsindpakningen ligge til side for øjeblikket (det vender vi tilbage til). Du kan bruge en simpel OPDATERING med en
RETURNING
klausul:
UPDATE tbl
SET value1 = 'something_new'
WHERE id = 123
RETURNING row_to_json(ROW(value1, value2));
RETURNING
klausul tillader vilkårlige udtryk, der involverer kolonner i den opdaterede række. Det er kortere og billigere end en datamodificerende CTE.
Det resterende problem:rækkekonstruktøren ROW(...)
bevarer ikke kolonnenavne (hvilket er en kendt svaghed), så du får generiske nøgler i din JSON-værdi:
row_to_json
{"f1":"something_new","f2":"what ever is in value2"}
I Postgres 9.3 skal du bruge en CTE, en anden funktion til at indkapsle det første trin eller en cast til en veldefineret rækketype. Detaljer:
I Postgres 9.4 bare brug json_build_object()
eller json_object()
:
UPDATE tbl
SET value1 = 'something_new'
WHERE id = 123
RETURNING json_build_object('value1', value1, 'value2', value2);
Eller:
...
RETURNING json_object('{value1, value2}', ARRAY[value1, value2]);
Nu får du originale kolonnenavne eller hvad du nu har valgt som nøglenavne:
row_to_json
{"value1":"something_new","value2":"what ever is in value2"}
Det er nemt at pakke dette ind i en funktion, som bringer os til dit andet problem ...
Navnekonflikt
I din oprindelige funktion bruger du identiske navne til funktionsparametre og kolonnenavne. Dette er generelt en meget dårlig idé . Du bliver nødt til nøje at forstå, hvilken identifikator der kommer først i hvilket omfang.
I det foreliggende tilfælde er resultatet fuldstændig nonsens:
Create Or Replace Function ExampleTable_Update (id bigint, value1 text) Returns
...
Update ExampleTable
Set Value1 = value1
Where id = id
Returning Value1, Value2;
...
$$ Language SQL;
Mens du ser ud til at forvente, at den anden forekomst af id
ville referere til funktionsparameteren, gør den ikke. Kolonnenavnet kommer først inden for rammerne af en SQL-sætning, den anden instans refererer til kolonnen. resulterer i et udtryk, der altid er sandt
undtagen NULL-værdier i id
. Derfor vil du opdatere alle rækker , hvilket kan føre til katastrofalt tab af data .Hvad værre er, kan du måske ikke engang indse det før senere, fordi SQL-funktionen returnerer én vilkårlig række som defineret af RETURNING
klausul af funktionen (returnerer én række, ikke et sæt rækker).
I dette særlige tilfælde ville du være "heldig", fordi du også har value1 =value1
, som overskriver kolonnen med dens allerede eksisterende værdi, hvilket effektivt gør .. ingenting på en meget dyr måde (medmindre triggere gør noget). Du kan blive forundret over at få en vilkårlig række med en uændret værdi1
som resultat.
Så lad være.
Undgå potentielle navnekonflikter som denne, medmindre du ved præcis, hvad du gør (hvilket naturligvis ikke er tilfældet). En konvention, jeg kan lide, er at sætte en understregning foran for parameter- og variabelnavne i funktioner, mens kolonnenavne aldrig starter med en understregning. I mange tilfælde kan du bare bruge positionsreferencer for at være entydige:$1
, $2
, ..., men det omgår kun den ene halvdel af problemet. Enhver metode er god, så længe du undgår navnekonflikter . Jeg foreslår:
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;
Bemærk også, at dette returnerer den faktiske kolonneværdi i værdi1
efter OPDATERING
, som muligvis er det samme som din inputparameter _value1
. Der kan være databaseregler eller triggere, der forstyrrer ...