PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Postgres-Funktion, die eine Zeile als JSON-Wert zurückgibt

Ich sehe zwei Hauptprobleme:
1. Sie können kein UPDATE setzen überhaupt in eine Unterabfrage . Sie könnten das mit einer Datenänderung lösen CTE wie Patrick demonstriert , aber das ist teurer und ausführlicher als für den vorliegenden Fall erforderlich.
2. Sie haben einen potenziell gefährlichen Namenskonflikt , das wurde noch nicht behandelt.

Bessere Abfrage / Funktion

Lassen wir den SQL-Funktionswrapper für den Moment beiseite (wir kommen darauf zurück). Sie können ein einfaches UPDATE verwenden mit einem RETURNING Klausel:

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

Der RETURNING -Klausel erlaubt beliebige Ausdrücke mit Spalten der aktualisierten Zeile. Das ist kürzer und billiger als ein datenmodifizierender CTE.

Das verbleibende Problem:der Zeilenkonstruktor ROW(...) behält keine Spaltennamen bei (was eine bekannte Schwachstelle ist), sodass Sie generische Schlüssel in Ihrem JSON-Wert erhalten:

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

In Postgres 9.3 benötigen Sie eine andere CTE-Funktion, um den ersten Schritt oder eine Umwandlung in einen wohldefinierten Zeilentyp zu kapseln. Einzelheiten:

In Postgres 9.4 Verwenden Sie einfach json_build_object() oder json_object() :

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

Oder:

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

Jetzt erhalten Sie die ursprünglichen Spaltennamen oder was auch immer Sie als Schlüsselnamen gewählt haben:

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

Es ist einfach, dies in eine Funktion zu packen, was uns zu Ihrem zweiten Problem bringt ...

Namenskonflikt

In Ihrer ursprünglichen Funktion verwenden Sie identische Namen für Funktionsparameter und Spaltennamen. Dies ist im Allgemeinen eine sehr schlechte Idee . Sie müssten genau verstehen, welche Kennung in welchem ​​Bereich zuerst kommt.

Im vorliegenden Fall ist das Ergebnis völliger Unsinn:

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

Während Sie zu erwarten scheinen, dass die zweite Instanz von id auf den Funktionsparameter verweisen würde, tut es nicht. Der Spaltenname steht im Rahmen einer SQL-Anweisung an erster Stelle, die zweite Instanz referenziert die Spalte. was zu einem Ausdruck führt, der immer true ist außer NULL-Werte in id . Folglich würden Sie alle Zeilen aktualisieren , was zu einem katastrophalen Datenverlust führen kann .Was noch schlimmer ist, Sie merken es vielleicht erst später, weil die SQL-Funktion eins zurückgibt beliebige Zeile wie durch RETURNING definiert -Klausel der Funktion (gibt eins zurück Zeile, nicht eine Reihe von Zeilen).

In diesem speziellen Fall würden Sie "Glück" haben, weil Sie auch value1 = value1 haben , die die Spalte mit ihrem bereits vorhandenen Wert überschreibt und auf sehr teure Weise effektiv nichts tut (es sei denn, Trigger tun etwas). Sie könnten verwirrt sein, eine beliebige Zeile mit einem unveränderten value1 zu erhalten als Ergebnis.

Also nicht.

Vermeiden Sie mögliche Namenskonflikte wie diesen, es sei denn, Sie wissen genau, was Sie tun (was offensichtlich nicht der Fall ist). Eine Konvention, die ich mag, besteht darin, Parameter- und Variablennamen in Funktionen einen Unterstrich voranzustellen, während Spaltennamen niemals mit einem Unterstrich beginnen. In vielen Fällen können Sie einfach Positionsreferenzen verwenden, um eindeutig zu sein:$1 , $2 , ..., aber das umgeht nur die eine Hälfte des Problems. Jede Methode ist gut, solange Sie Namenskonflikte vermeiden . Ich schlage vor:

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;

Beachten Sie auch, dass dies den tatsächlichen Spaltenwert zurückgibt in value1 nach dem UPDATE , der mit Ihrem Eingabeparameter _value1 identisch sein kann oder nicht . Möglicherweise stören Datenbankregeln oder Trigger ...