将函数返回的记录拆分为多列

Split function-returned record into multiple columns

在基本的 Postgres 函数教程中有一个带有 OUT 参数的示例,如下所示:

create or replace function hi_lo(a numeric, 
                                 b numeric, 
                                 c numeric, 
                                 OUT hi numeric, 
                                 OUT lo numeric)
as $$
begin
hi := greatest(a, b, c);
lo := least(a, b, c);
end; $$
language plpgsql;

然后结果看起来像

select hi_lo(2, 3, 4);
-- returns one column, "hi_lo" with value "(4, 2)".

select * from hi_lo(2, 3, 4);
-- returns two columns, "hi" / 4 and "lo" / 2.

但是假设您要在执行连接后产生的列上执行该函数,并且您无权修改该函数或使用替代函数?例如,使用一些玩具数据:

select hi_lo(a.actor_id, length(a.name), ma.movie_id) 
from 
    actors a 
join 
    movies_actors ma 
on 
    a.actor_id = ma.movie_id 
limit 10;

returns 导致单个列 "hi_lo" 具有 2 元组值。

将查询括在括号中并尝试从中获取 select * 不会更改输出格式。所以

select *
from (
    select hi_lo(a.actor_id, length(a.name), ma.movie_id) 
    from 
        actors a 
    join 
        movies_actors ma 
    on 
        a.actor_id = ma.movie_id 
    limit 10;
) rr

不影响结果形状。

以下尝试导致错误 "subquery must return only one column"

select (
    select * from hi_lo(a.actor_id, length(a.name), ma.movie_id)
) 
from 
    actors a 
join 
    movies_actors ma 
on
    a.actor_id = ma.movie_id 
limit 10;

最后,我也尝试了 unnest 但它给出了参数类型错误,因为元组值未被视为数组。

无法将函数求值移动到 from 部分时,如何在输出中实现多列?

Postgres 9.3 或更高版本

最好用 LATERAL 连接解决:

SELECT *
FROM   actors a 
JOIN   movies_actors ma on a.actor_id = ma.movie_id 
LEFT   JOIN LATERAL hi_lo(a.actor_id, length(a.name), ma.movie_id) x ON true
LIMIT  10;

避免函数的重复计算(对于输出中的每一列 - 无论哪种方式,都必须为每个输入行调用该函数)。参见:

  • How to avoid multiple function evals with the (func()).* syntax in an SQL query?

LEFT JOIN LATERAL ... ON true 以避免在右侧的函数 return 没有行时从左侧删除行。参见:

寻址 :

only the expanded columns produced by the function call

SELECT x.*  -- that's all!
FROM   actors a 
JOIN   movies_actors ma on a.actor_id = ma.movie_id 
LEFT   JOIN LATERAL hi_lo(a.actor_id, length(a.name), ma.movie_id) x ON true
LIMIT  10;

但是因为你不关心其他列,你可以简化为:

SELECT x.*
FROM   actors a 
JOIN   movies_actors ma on a.actor_id = ma.movie_id 
     , hi_lo(a.actor_id, length(a.name), ma.movie_id) x
LIMIT  10;

这是隐含的CROSS JOIN LATERAL。如果函数实际上可以 return 偶尔“无行”,结果可能会有所不同:我们没有得到行的 NULL 值,这些行只是被消除了 - LIMIT 没有计算它们更多

旧版本(或一般)

您也可以使用正确的语法分解复合类型:

SELECT *, <b>(</b>hi_lo(a.actor_id, length(a.name), ma.movie_id)<b>).*</b>  -- note extra parentheses!
FROM   actors a 
JOIN   movies_actors ma on a.actor_id = ma.movie_id 
LIMIT  10;

缺点是由于顶部提到的 Postgres 查询规划器的弱点,函数输出中的每一列都会计算一次函数。最好将调用移动到子查询或 CTE 中,并分解外部 SELECT 中的行类型。喜欢:

SELECT actor_id, movie_id, (x).*  -- explicit column names for the rest
FROM  (
   SELECT *, hi_lo(a.actor_id, length(a.name), ma.movie_id) AS x
   FROM   actors a 
   JOIN   movies_actors ma on a.actor_id = ma.movie_id 
   LIMIT  10
   ) sub;

但是您必须为各个列命名,除非您对结果中的行类型满意,否则不能使用 SELECT *。 相关:

  • Avoid multiple calls on same function when expanding composite result