Ik zie twee grote problemen:
1. U kunt geen UPDATE
put plaatsen in een subquery helemaal . Je zou dat kunnen oplossen met een data-modifying CTE
zoals Patrick demonstreert
, maar dat is duurder en uitgebreider dan nodig is voor de onderhavige zaak.
2. U heeft een mogelijk gevaarlijk naamgevingsconflict , dat is nog niet behandeld.
Betere zoekopdracht / functie
Laten we de wrapper van de SQL-functie voorlopig opzij (daar komen we op terug). U kunt een eenvoudige UPDATE
. gebruiken met een RETURNING
clausule:
UPDATE tbl
SET value1 = 'something_new'
WHERE id = 123
RETURNING row_to_json(ROW(value1, value2));
De RETURNING
clausule staat willekeurige uitdrukkingen toe met betrekking tot kolommen van de bijgewerkte rij. Dat is korter en goedkoper dan een gegevensmodificerende CTE.
Het resterende probleem:de rijconstructor ROW(...)
bewaart geen kolomnamen (wat een bekende zwakte is), dus u krijgt generieke sleutels in uw JSON-waarde:
row_to_json
{"f1":"something_new","f2":"what ever is in value2"}
In Postgres 9.3 zou je een andere CTE-functie nodig hebben om de eerste stap of een cast in een goed gedefinieerd rijtype in te kapselen. Details:
In Postgres 9,4 gebruik gewoon json_build_object()
of json_object()
:
UPDATE tbl
SET value1 = 'something_new'
WHERE id = 123
RETURNING json_build_object('value1', value1, 'value2', value2);
Of:
...
RETURNING json_object('{value1, value2}', ARRAY[value1, value2]);
Nu krijg je originele kolomnamen of wat je ook hebt gekozen als sleutelnamen:
row_to_json
{"value1":"something_new","value2":"what ever is in value2"}
Het is gemakkelijk om dit in een functie in te pakken, wat ons bij je tweede probleem brengt ...
Naamconflict
In je oorspronkelijke functie gebruik je identieke namen voor functieparameters en kolomnamen. Dit is over het algemeen een zeer slecht idee . U moet goed begrijpen welke identifier eerst komt in welke reikwijdte.
In het onderhavige geval is het resultaat volslagen onzin:
Create Or Replace Function ExampleTable_Update (id bigint, value1 text) Returns
...
Update ExampleTable
Set Value1 = value1
Where id = id
Returning Value1, Value2;
...
$$ Language SQL;
Hoewel je lijkt te verwachten dat het tweede exemplaar van id
zou verwijzen naar de functieparameter, maar dat doet het niet. De kolomnaam komt eerst binnen het bereik van een SQL-instructie, de tweede instantie verwijst naar de kolom. wat resulteert in een uitdrukking die altijd true
. is behalve voor NULL-waarden in id
. Daarom zou u alle rijen . bijwerken , wat kan leiden tot catastrofaal gegevensverlies .Erger nog, je realiseert het je misschien pas later, omdat de SQL-functie één zal retourneren willekeurige rij zoals gedefinieerd door de RETURNING
clausule van de functie (retourneert één rij, niet een reeks rijen).
In dit specifieke geval zou je "geluk" hebben, omdat je ook value1 = value1
hebt , die de kolom overschrijft met zijn reeds bestaande waarde, effectief .. niets doet op een zeer dure manier (tenzij triggers iets doen). Het is misschien een raadsel om een willekeurige rij te krijgen met een ongewijzigde value1
als resultaat.
Dus niet doen.
Vermijd mogelijke naamgevingsconflicten zoals deze, tenzij u precies weet wat u doet (wat uiteraard niet het geval is). Een conventie die ik leuk vind, is om een onderstrepingsteken voor parameter- en variabelenamen in functies voor te zetten, terwijl kolomnamen nooit beginnen met een onderstrepingsteken. In veel gevallen kunt u alleen positionele verwijzingen gebruiken om eenduidig te zijn:$1
, $2
, ..., maar dat omzeilt slechts de helft van het probleem. Elke methode is goed zolang je naamconflicten vermijdt . Ik stel voor:
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;
Merk ook op dat dit de werkelijke kolomwaarde . retourneert in value1
na de UPDATE
, die al dan niet hetzelfde kan zijn als uw invoerparameter _value1
. Er kunnen databaseregels of triggers zijn die interfereren ...