sql >> Database >  >> RDS >> PostgreSQL

Postgres-functie die een rij retourneert als JSON-waarde

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



  1. MySql:meerkeuzegegevens opslaan in database

  2. Op cursor gebaseerde records in PostgreSQL

  3. Entity Framework:hoe een aantal gerelateerde tabellen in een database opvragen en een enkele reis maken?

  4. Tabellen samenvoegen op basis van de maximale waarde