Функция Postgres, возвращающая строку как значение JSON

Я новичок в PG и пытаюсь конвертировать из MSSQL.

Я работаю над функцией, которая будет возвращать результаты JSON. Этот работает:

Create Or Replace Function ExampleTable_SelectList()
Returns JSON As
$$
  Select array_to_json(array_agg(row_to_json(t))) From
    (Select id, value1, value2, From ExampleTable) t
$$ Language SQL;

Теперь я хочу вызвать обновление, которое возвращает значение, и преобразовать это значение в JSON для возврата. Итак, этот выдает ошибку по команде set.

Create Or Replace Function ExampleTable_Update (id bigint, value1 text)
  Returns JSON As
$$
  Select row_to_json(t) From
  (
    Update ExampleTable
    Set Value1 = value1
    Where id= id
    Returning Value1, Value2;
  ) t
$$ Language SQL;

Я подозреваю, что Postgres не допускает использование оператора UPDATE в качестве подзапроса. Есть ли что-то вокруг этого?


person Arotae    schedule 30.06.2015    source источник
comment
опубликуйте ошибку, которую вы получаете.   -  person JP Moresmau    schedule 30.06.2015
comment
RETURNING является расширением postgresql и работает не везде, где можно использовать подзапросы. Попробуйте использовать CTE -- < i>Эта команда соответствует стандарту SQL, за исключением того, что предложения FROM и RETURNING являются расширениями PostgreSQL, как и возможность использования WITH с UPDATE.   -  person pozs    schedule 30.06.2015
comment
Вы забыли указать свою версию Postgres, которая необходима для лучшего ответа. Можно считать текущую версию 9.4? Определение таблицы также могло бы многое прояснить (что вы получаете с помощью \d "ExampleTable" in psql или что вы видите на панели SQL в pgAdmin для таблицы.   -  person Erwin Brandstetter    schedule 30.06.2015
comment
Это 9,4. Похоже, CTE дает результат, который работает:   -  person Arotae    schedule 30.06.2015
comment
Создать или заменить функцию ExampleTable_Update (id bigint, текст value1) Возвращает JSON как $$ With t As ( Обновить ExampleTable Set Value1 = $2 Где id = $1 Возвращает Value1, Value2; ) t Select row_to_json(s) From (выберите Value1, Value2 из t) s $$ Язык SQL;   -  person Arotae    schedule 30.06.2015


Ответы (2)


Я вижу две основные проблемы:
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);

Or:

...
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 не вернет одну произвольную строку, как определено предложением 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. Могут быть правила базы данных или триггеры, мешающие...

person Erwin Brandstetter    schedule 30.06.2015
comment
Большое спасибо за ваш совет. Я переключился на позиционную ссылку во время тестирования, но я бы предпочел именованную ссылку. Я реализовал все ваши предложения. Функция красивая, и результаты идеальны. Возврат json_build_object возвращает меня к более чистому синтаксису с большим контролем результатов. - person Arotae; 01.07.2015

Вам нужно поместить оператор UPDATE в CTE:

CREATE OR REPLACE FUNCTION ExampleTable_Update (id bigint, value1 text) RETURNS json AS $$
  WITH t(Value2) AS (
    UPDATE ExampleTable
    SET Value1 = $2
    WHERE id = $1
    RETURNING Value2)
  SELECT row_to_json($2, Value2) 
  FROM t;
$$ LANGUAGE sql;

Обратите внимание, что я использую позиционные параметры $1 и $2 для параметров функции. Имена этих параметров совпадают с именами столбцов в таблице, и это, как правило, плохая идея из-за возможных конфликтов разрешения имен; см. ответ Эрвина Брандштеттера для более подробного объяснения.

person Patrick    schedule 30.06.2015
comment
@Arotae: вам не нужен CTE, а row_to_json($2, Value2) потенциально неверно / вводит в заблуждение, потому что то, что находится в столбце после UPDATE, не обязательно совпадает с $2. Подумайте о триггерах... Кроме того, этот код позволяет избежать скрытого конфликта имен в вопросе, используя позиционную ссылку $1, но это следует по крайней мере упомянуть... - person Erwin Brandstetter; 30.06.2015
comment
@ErwinBrandstetter: вопрос был об использовании оператора UPDATE в подзапросе. Если OP хочет стиль подзапроса UPDATE, то единственный способ - это CTE, который можно рассматривать как простое синтаксическое удобство для подзапроса в нерекурсивной форме. Точно так же, как ExampleTable может иметь триггеры, о которых не упоминал OP (я действительно понял эту проблему, как только отправил свое редактирование), запрос SELECT может быть гораздо более сложным, включая таблицы и/или столбцы, не используемые в UPDATE. Объяснил использование позиционных параметров при редактировании ответа. - person Patrick; 01.07.2015
comment
Вы правы насчет ОБНОВЛЕНИЯ в CTE, никаких аргументов по этому поводу. Но на самом деле вопрос в том, как вернуть значение JSON после UPDATE. Подзапрос или CTE - это просто средство для достижения цели, я утверждаю, что здесь нам не нужно ни то, ни другое. Кроме того, просмотр CTE как mere syntactic convenience for a sub-query in the non-recursive form будет отфильтровывать CTE как более дорогостоящие, всегда материализовывать временные результаты и действовать как барьеры оптимизации (последний бит здесь не имеет значения). И есть некоторые другие различия в угловых случаях. По сути, используйте CTE только тогда, когда вам это нужно (что достаточно распространено). - person Erwin Brandstetter; 01.07.2015