【问题标题】:Split function-returned record into multiple columns将函数返回的记录拆分为多列
【发布时间】:2016-11-12 20:51:16
【问题描述】:

在基本的 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;

在单个列中返回结果“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

不影响结果形状。

以下尝试导致错误“子查询必须只返回一列”

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 版本?

标签: sql postgresql plpgsql record


【解决方案1】:

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;

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

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

寻址your comment:

仅由函数调用产生的扩展列

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。如果函数实际上可以偶尔返回“无行”,结果可能会有所不同:我们没有得到行的 NULL 值,这些行只是被消除了 - LIMIT 不再计算它们。

旧版本(或一般)

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

SELECT *, (hi_lo(a.actor_id, length(a.name), ma.movie_id)).*  -- 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 *,除非您对结果中的行类型没问题。 相关:

【讨论】:

  • 这非常有帮助。一个后续问题是如何限制输出列,以便它们只是函数调用产生的扩展列,而不是连接中的所有列加上函数调用返回的列。使用select * from hi_lo 时,它会直接处理此问题,但当前查询的结果是此数据的 6 列(连接中的 4 个附加列)。特别是,假设您事先不知道列名,或者该函数可能会在您的控制之外发生变化。
  • @Mr.F:我添加了另一个解决方案。
  • 这非常有用,尤其是关于将调用包装在子查询中的提示。这比仅在没有包装的情况下分解复合类型快约 10 倍。查询计划器中的什么错误使它变得如此之慢?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-11-03
  • 2010-12-22
  • 2022-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-14
相关资源
最近更新 更多