Виждам два основни проблема:
1. Вие не можете да поставите UPDATE
в подзаявка изобщо . Можете да разрешите това с модифициране на данни CTE
като Патрик демонстрира
, но това е по-скъпо и многословно, отколкото е необходимо за конкретния случай.
2. Имате потенциално опасен конфликт на име , който все още не е разгледан.
По-добра заявка/функция
Оставяме обвивката на SQL функцията настрана за момента (ще се върнем към това). Можете да използвате просто UPDATE
с RETURNING
клауза:
UPDATE tbl
SET value1 = 'something_new'
WHERE id = 123
RETURNING row_to_json(ROW(value1, value2));
RETURNING
клауза позволява произволни изрази, включващи колони от актуализирания ред. Това е по-кратко и по-евтино от CTE, модифициращ данни.
Оставащият проблем:конструкторът на ред ROW(...)
не запазва имена на колони (което е известна слабост), така че получавате общи ключове във вашата JSON стойност:
row_to_json
{"f1":"something_new","f2":"what ever is in value2"}
В Postgres 9.3 ще ви трябва CTE друга функция за капсулиране на първата стъпка или преобразуване към добре дефиниран тип ред. Подробности:
В Postgres 9.4 просто използвайте json_build_object()
или json_object()
:
UPDATE tbl
SET value1 = 'something_new'
WHERE id = 123
RETURNING json_build_object('value1', value1, 'value2', value2);
Или:
...
RETURNING json_object('{value1, value2}', ARRAY[value1, value2]);
Сега получавате оригинални имена на колони или каквото сте избрали като имена на ключове:
row_to_json
{"value1":"something_new","value2":"what ever is in value2"}
Лесно е да обвиете това във функция, което ни води до втория ви проблем ...
Конфликт при именуване
Във вашата оригинална функция използвате идентични имена за функционални параметри и имена на колони. Като цяло това е много лоша идея . Ще трябва да разберете отблизо кой идентификатор е на първо място в кой обхват.
В разглеждания случай резултатът е пълна глупост:
Create Or Replace Function ExampleTable_Update (id bigint, value1 text) Returns
...
Update ExampleTable
Set Value1 = value1
Where id = id
Returning Value1, Value2;
...
$$ Language SQL;
Въпреки че изглежда очаквате, че второто копие на id
ще препраща към параметъра на функцията, но не го прави. Името на колоната е първо в обхвата на SQL оператор, а второто копие препраща към колоната. което води до израз, който винаги е true
с изключение на NULL стойности в id
. Следователно ще актуализирате всички редове , което може да доведе до катастрофална загуба на данни .Което е по-лошо, може дори да не го осъзнаете до по-късно, защото SQL функцията ще върне one произволен ред, както е дефиниран от RETURNING
клауза на функцията (връща едно ред, а не набор от редове).
В този конкретен случай ще имате "късмет", защото също така имате value1 = value1
, който презаписва колоната с нейната предварително съществуваща стойност, като ефективно прави .. нищо по много скъп начин (освен ако тригерите не направят нещо). Може да сте озадачени да получите произволен ред с непроменена value1
като резултат.
Така че недейте.
Избягвайте потенциални конфликти при именуване като този, освен ако не знаете точно какво правите (което очевидно не е така). Една конвенция, която харесвам, е да добавям долна черта към имена на параметри и променливи във функциите, докато имената на колони никога не започват с долна черта. В много случаи можете просто да използвате позиционни препратки, за да бъдете недвусмислени:$1
, $2
, ..., но това заобикаля само половината от проблема. Всеки метод е добър, стига да избягвате конфликти в имената . Предлагам:
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;
Също така имайте предвид, че това връща действителната стойност на колоната в value1
след UPDATE
, което може или не може да бъде същото като вашия входен параметър _value1
. Може да има правила за база данни или задействания, които пречат...