【问题标题】:RETURNING rows using unnest()?使用 unnest() 返回行?
【发布时间】:2015-11-23 06:27:59
【问题描述】:

我正在尝试在执行UPDATE 之后返回一组行。

类似的东西。

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 抱怨,这是正确的:

在不能接受集合的上下文中调用的集合值函数

我应该如何实现这个?

RETURNINGSELECTed数组中UPDATE之后的一组行?

我知道一个函数可以使用RETURNS SETOF 来实现这一点,但如果可能的话,我宁愿不这样做。

【问题讨论】:

    标签: sql database postgresql set-returning-functions sql-returning


    【解决方案1】:

    使用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;
    

    【讨论】:

    • 啊哈!太感谢了。还有一件事。如果 noti_record_type 是自定义类型,我如何通过每个类型键将其解包到正确的列?目前它返回一个名为 unnest 的列。
    • select (unnest(notis)).col1, (unnest(notis)).col2 from upd
    【解决方案2】:

    @klin provided,使用data-modifying CTE

    但是,虽然您可以SELECT 列表中使用集合返回函数(出于历史原因),但如果可能的话,最好将其移至FROM 列表中。自从LATERAL 加入 Postgres 9.3 以来,这几乎总是可能的。

    特别是如果您需要提取 多个 列(从您评论的 行类型)。多次调用unnest() 也是低效的。

    WITH upd AS (
       UPDATE notis n
       SET    notis = '{}'::noti_record_type[]  -- explicit cast optional
       FROM (
          SELECT 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

    此外,同一SELECT 中的多个集合返回函数可能会表现出令人惊讶的行为。避免这种情况。如果您确实需要并行取消嵌套 多个 数组:

    相关:

    解释我之前的错误

    当从集合返回函数将行类型(或复合或记录类型)分配给列列表时,Postgres 有一个奇怪。人们可能期望行类型字段被视为 one 列并分配给相应的列,但事实并非如此。它会自动分解(仅一行层!)并逐个元素分配。

    所以这不能按预期工作:

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

    但确实如此 (like @klin commented):

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

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

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

    还有另一个奇怪:当只为具有一列的集合返回函数提供 一个 别名时,它似乎被用作table 别名 and 作为 column 别名。 SELECT 列表中的名称返回 column 值。所以所有这些都做同样的事情:

    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);
    

    但是,一旦有多个元素,行包装器就会被保留:

    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 不会返回复合类型,而只会返回(重命名的)第一个元素。我粗心地期待行类型并尝试使用(my_row).* 进行分解 - 但它只返回第一个元素。

    再说一遍:

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

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

    SQL Fiddle 一起玩。

    在 Postgres 9.4+(不在小提琴中)中,unnest() 的新形式采用多个数组参数:

    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 (my_row).* from upd u, unnest(u.notis) my_row
    • @klin:你是对的,抱歉我没有测试。我现在使用更简单的变体并添加了解释。
    【解决方案3】:

    大概 对于:

    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);
    

    【讨论】:

    • 欢迎来到 StackOverflow!请格式化您的代码,使其更具可读性!
    猜你喜欢
    • 2020-08-17
    • 2022-06-30
    • 1970-01-01
    • 2022-10-13
    • 2020-10-20
    • 1970-01-01
    • 2017-03-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多