使用 unnest() 返回行?

RETURNING rows using unnest()?

在完成 UPDATE 后,我正在尝试 return 一组行。

像这样。

UPDATE Notis new_noti SET notis = '{}'::noti_record_type[] 
FROM (SELECT * FROM Notis WHERE user_id = 2 FOR UPDATE) old_noti 
WHERE old_noti.user_id = new_noti.user_id RETURNING unnest(old_noti.notis);

但是 postgres 抱怨,这是正确的:

set-valued function called in context that cannot accept a set

我应该如何着手实施它?

也就是说,RETURNING 来自 SELECTed 数组的一组行在 UPDATE?

之后

我知道函数可以使用 RETURNS SETOF 实现此目的,但如果可能的话我宁愿不要这样做。

使用WITH statement:

WITH upd AS (
    UPDATE Notis new_noti SET notis = '{}'::noti_record_type[] 
    FROM (SELECT * FROM Notis WHERE user_id = 2 FOR UPDATE) old_noti 
    WHERE old_noti.user_id = new_noti.user_id RETURNING old_noti.notis
    )
SELECT unnest(notis) FROM upd;

使用 data-modifying CTE.
可以 SELECT 列表中使用 set-returning 函数,但是将它移动到 FROM 列表中使用 LATERAL 自 Postgres 9.3 以来的子查询。特别是如果您需要提取 多个 列(从 行类型 中提取,就像您评论的那样)。多次调用 unnest() 也是低效的。

WITH upd AS (
   UPDATE notis n
   SET    notis = '{}'::noti_record_type[]  -- explicit cast optional
   FROM  (
      SELECT user_id, notis
      FROM   notis
      WHERE  user_id = 2
      FOR    UPDATE
      ) old_n
    WHERE old_n.user_id = n.user_id
    RETURNING old_n.notis
    )
SELECT n.*
FROM   upd u, unnest(u.notis) n;  -- implicit CROSS JOIN LATERAL

如果数组可以为空并且您想保留空/NULL 结果,请使用 LEFT JOIN LATERAL ... ON true。参见:

  • Call a set-returning function with an array argument multiple times

此外,同一个 SELECT 中的多个 set-returning 函数会表现出令人惊讶的行为。避免这种情况。 这已使用 Postgres 10 进行了清理。请参阅:

在 Postgres 10 之前和之后并行取消嵌套 多个 数组的替代方法:

相关:

  • Return pre-UPDATE column values using SQL only

复合/行值的行为

Postgres 在从 set-returning 函数向列列表分配行类型(或复合或记录类型)时有一个 oddity。人们可能希望行类型字段被视为 one 列并分配给相应的列,但事实并非如此。它会自动分解(仅一行层!)并逐个元素分配。

所以这没有按预期工作:

<strike>SELECT (my_row).*
FROM   upd u, unnest(u.notis) n(my_row);</strike>

但这确实 (:

SELECT (my_row).*
FROM   upd u, unnest(u.notis) my_row;

或者我最终使用的更简单的版本:

SELECT n.*
FROM   upd u, unnest(u.notis) n;

另一个奇怪之处:具有单个字段 的复合(或行)类型会自动分解。因此,table 别名和列别名最终在外部 SELECT 列表中执行相同的操作:

SELECT n FROM unnest(ARRAY[1,2,3]) n;
SELECT n FROM unnest(ARRAY[1,2,3]) n(n);
SELECT n FROM unnest(ARRAY[1,2,3]) t(n);
SELECT t FROM unnest(ARRAY[1,2,3]) t(n);  -- except output column name is "t"

对于多个字段,保留行包装器:

SELECT t FROM unnest(ARRAY[1,2,3]) WITH ORDINALITY t(n);  -- requires 9.4+

迷茫?还有更多。对于复合类型(手头的情况),如:

CREATE TYPE my_type AS (id int, txt text);

虽然这按预期工作:

SELECT n FROM unnest(ARRAY[(1, 'foo')::my_type, (2, 'bar')::my_type]) n;

您在这里有 惊喜

SELECT n FROM unnest(ARRAY[(1, 'foo')::my_type, (2, 'bar')::my_type]) n(n);

这就是我遇到的错误:提供列列表时,Postgres 分解行并逐一分配提供的名称。在 SELECT 列表中引用 n 并不是 return 复合类型,而只是(重命名的)第一个元素。我错误地预期了行类型并尝试使用 (my_row).* 进行分解 - 尽管如此,它只有 return 是第一个元素。

再说一遍:

SELECT t FROM unnest(ARRAY[(1, 'foo')::my_type, (2, 'bar')::my_type]) t(n);

(请注意,第一个元素已重命名为 "n"!)

使用新形式的 unnest() 接受多个数组参数(Postgres 9.4+):

SELECT *
FROM   unnest(ARRAY[(1, 'foo')::my_type, (2, 'bar')::my_type]
            , ARRAY[(3, 'baz')::my_type, (4, 'bak')::my_type]) n;

仅前两个输出列的列别名:

SELECT *
FROM   unnest(ARRAY[(1, 'foo')::my_type, (2, 'bar')::my_type]
            , ARRAY[(3, 'baz')::my_type, (4, 'bak')::my_type]) n(a, b);

所有输出列的列别名:

SELECT *
FROM   unnest(ARRAY[(1,'foo')::my_type, (2,'bar')::my_type]
            , ARRAY[(3,'baz')::my_type, (4,'bak')::my_type]) n(a,b,c,d);

db<>fiddle here
sqlfiddle

大概 对于:

SELECT *
FROM   unnest (ARRAY[(1, 'foo')::my_type, (2, 'bar')::my_type]
             , ARRAY[(3, 'baz')::my_type, (4, 'bak')::my_type]) n(a, b);

使用:

SELECT *
FROM   unnest (ARRAY[(1, 'foo')::text, (2, 'bar')::text]
             , ARRAY[(3, 'baz')::text, (4, 'bak')::text]) WITH ORDINALITY AS t(first_col, second_col);