Postgres 函数返回一行作为 JSON 值

Postgres function returning a row as JSON value

我是 PG 的新手,正在尝试从 MSSQL 转换。

我正在开发一个将 return 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;

现在,我想调用 can 更新 return 的值并将该值转换为 JSON 到 return。所以,这个在 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 语句作为子查询。反正有没有?

您需要将 UPDATE 语句放入 CTE:

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

请注意,我使用 positional parameters </code> 和 <code> 作为函数参数。这些参数的名称与 table 中的列名称相同,这通常不是一个好主意,因为可能会发生名称解析冲突;请参阅 Erwin Brandstetter 的回答以获得更详尽的解释。

我看到两个主要问题:

  1. 根本无法将 UPDATE 放入子查询 。您可以使用 data-modifying CTE like 来解决这个问题,但是对于手头的情况来说,这比必要的更昂贵和冗长。

  2. 您有一个潜在危险的命名冲突,尚未解决。

更好的查询/功能

暂时将 SQL 函数包装放在一边(我们会回过头来)。您可以使用带有 RETURNING 子句的简单 UPDATE

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 另一个函数来封装第一步或转换为明确定义的行类型。详情:

  • Return multiple columns of the same row as JSON array of objects

在 Postgres 9.4 中只需使用 json_build_object() or 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 <b>id = id</b>
    Returning Value1, Value2;
...
$$ Language SQL;

虽然您似乎期望 id 的第二个实例会引用函数参数,但事实并非如此。列名首先出现在 SQL 语句的范围内,第二个实例引用该列。导致表达式始终为 true,但 id 中的 NULL 值除外。因此,您将更新所有行,这可能会导致灾难性数据丢失。 更糟糕的是,您甚至可能直到后来才意识到这一点,因为 SQL 函数会将 return one 任意行作为由函数的 RETURNING 子句定义(returns one 行,而不是一组行)。

在这种特殊情况下,您会很“幸运”,因为您还有 value1 = value1,它会用其先前存在的值覆盖该列,有效地...什么都不做,在非常昂贵的方式(除非触发器做某事)。您可能会感到困惑,因为结果是 value1 未更改的任意行。

所以,不要。

避免像这样潜在的命名冲突,除非你确切地知道你在做什么(显然不是这样)。我喜欢的一个约定是在函数中为参数和变量名称添加下划线,而列名称从不以下划线开头。在许多情况下,您可以只使用位置引用来明确:</code>、<code>、...,但这只能回避问题的一半。只要您避免命名冲突,任何方法都是好的。我建议:

CREATE OR REPLACE FUNCTION foo (_id bigint, _value1 text)
  RETURNS json AS
  LANGUAGE sql
$func$
UPDATE tbl
SET    value1 = _value1
WHERE  id     = _id
RETURNING json_build_object('value1', value1, 'value2', value2);
$func$;

另请注意,此 return 是 [=16] 之后 value1 中的 实际列值 =],它可能与您的输入参数 _value1 相同,也可能不同。可能有数据库规则或触发器干扰...