如何将一组行从一个函数传递到另一个函数?

Jos*_*lor 11 sql postgresql

概观

我正在使用PostgreSQL 9.1.14,我试图将函数的结果传递给另一个函数.一般的想法(细节,最小的例子,遵循)是我们可以写:

select * from (select * from foo ...) 
Run Code Online (Sandbox Code Playgroud)

我们可以在函数中抽象出子选择并从中进行选择:

create function foos() 
returns setof foo
language sql as $$
  select * from foo ...
$$;

select * from foos()
Run Code Online (Sandbox Code Playgroud)

有没有办法抽象出一个级别更远,以便能够做这样的事情(我知道函数实际上不能有setof类型的参数):

create function more_foos( some_foos setof foo )
language sql as $$
  select * from some_foos ...  -- or unnest(some_foos), or ???
$$:

select * from more_foos(foos())
Run Code Online (Sandbox Code Playgroud)

最小示例和尝试的变通方法

我正在使用PostgreSQL 9.1.14.这是一个最小的例子:

-- 1. create a table x with three rows                                                                                                                                                            
drop table if exists x cascade;
create table if not exists x (id int, name text);
insert into x values (1,'a'), (2,'b'), (3,'c');

-- 2. xs() is a function with type `setof x`
create or replace function xs()
returns setof x
language sql as $$
  select * from x
$$;

-- 3. xxs() should return the context of x, too
--    Ideally the argument would be a `setof x`,
--    but that's not allowed (see below).
create or replace function xxs(x[])  
returns setof x
language sql as $$
  select x.* from x
  join unnest($1) y
       on x.id = y.id
$$;
Run Code Online (Sandbox Code Playgroud)

当我加载这段代码时,我得到了表定义的预期输出,我可以xs()按照我的预期调用和选择.但是,当我试图通过结果xs()xxs(),我拿到"功能XXS(X)不存在"的错误:

db=> \i test.sql 
DROP TABLE
CREATE TABLE
INSERT 0 3
CREATE FUNCTION
CREATE FUNCTION

db=> select * from xs();
  1 | a
  2 | b
  3 | c

db=> select * from xxs(xs());
ERROR:  function xxs(x) does not exist
LINE 1: select * from xxs(xs());
                      ^
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
Run Code Online (Sandbox Code Playgroud)

我对"函数xxs(x)不存在"感到有点困惑; 因为返回类型xs()setof x,我希望它的返回类型是setof x(或者可能x[]),而不是x.在关于类型的投诉之后,我可以得到以下任何一种,但是虽然我可以使用任何一种定义,但select xxs(xs());我不能select * from xxs(xs());.

create or replace function xxs( x )
returns setof x
language sql as $$
  select x.* from x
  join unnest(array[$1]) y    -- unnest(array[...]) seems pretty bad
       on x.id = y.id
$$;
Run Code Online (Sandbox Code Playgroud)
create or replace function xxs( x )
returns setof x
language sql as $$
  select * from x
         where x.id in ($1.id)
$$;
Run Code Online (Sandbox Code Playgroud)
db=> select xxs(xs());
 (1,a)
 (2,b)
 (3,c)

db=> select * from xxs(xs());
ERROR:  set-valued function called in context that cannot accept a set
Run Code Online (Sandbox Code Playgroud)

摘要

将set-returns函数的结果传递给另一个函数的正确方法是什么?(我已经注意到create function ... xxs(setof x)...导致错误:错误:函数不能接受set参数,所以答案实际上不会将一组行从一个函数传递到另一个函数.)

小智 6

表功能

我使用SQL作为客户端和服务器语言(没有使用其他语言)执行非常高速,复杂的数据库迁移,所有这些都运行在服务器端,代码很少从数据库引擎中出现.表函数在我的工作中起着巨大的作用.我不使用"游标",因为它们太慢而无法满足我的性能要求,我所做的一切都是以结果集为导向的.表函数对我完全消除游标的使用,实现非常高的速度以及为减少代码量和提高简单性做出了巨大贡献.

总之,您可以使用查询引用两个(或更多)表函数从一个表函数到下一个传递数据.调用表函数的select查询结果集用作将数据从一个表函数传递到下一个表函数的管道.在我正在使用的DB2平台/版本上,基于快速查看9.1 Postgres手册,看起来同样如此,您只能将单行列值作为输入传递给任何表函数调用,正如你所发现的那样.但是,因为表函数调用发生在查询的结果集处理过程中,所以实现了将整个结果集传递给每个表函数调用的相同效果,尽管在数据库引擎管道中,数据只传递了一行一次到每个表功能.

表函数接受一行输入列,并将单个结果集返回到调用该函数的调用查询(即select).从表函数传回的结果集列成为调用查询结果集的一部分,因此可用作下一个表函数的输入,稍后在同一查询中引用,通常作为后续连接.第一个表函数的结果列作为输入(一次一行)提供给第二个表函数,后者将其结果集列返回到调用查询的结果集.第一个和第二个表函数结果集列现在都是调用查询结果集的一部分,现在可用作第三个表函数的输入(一次一行).每个表函数调用通过它返回的列来扩展调用查询的结果集.这可以继续,直到您开始对结果集的宽度施加限制,这可能因数据库引擎的不同而异.

考虑这个例子(在我使用DB2时可能与Postgres的语法要求或功能不匹配).这是我使用表函数的众多设计模式之一,是我认为非常具有说明性的一个更简单的模式之一,而且如果表函数在主流应用中占据主导地位,那么我预期它会具有广泛的吸引力(据我所知)不是,但我认为他们应该得到比他们更多的关注).

在此示例中,正在使用的表函数是:VALIDATE_TODAYS_ORDER_BATCH,POST_TODAYS_ORDER_BATCH和DATA_WAREHOUSE_TODAYS_ORDER_BATCH.在我使用的DB2版本上,将表函数包装在"TABLE(在此处放置表函数调用和参数)"中,但基于快速查看Postgres手册,您似乎省略了"TABLE()"包装器.

create table TODAYS_ORDER_PROCESSING_EXCEPTIONS as (

select      TODAYS_ORDER_BATCH.*
           ,VALIDATION_RESULT.ROW_VALID
           ,POST_RESULT.ROW_POSTED
           ,WAREHOUSE_RESULT.ROW_WAREHOUSED

from        TODAYS_ORDER_BATCH

cross join  VALIDATE_TODAYS_ORDER_BATCH ( ORDER_NUMBER, [either pass the remainder of the order columns or fetch them in the function]  ) 
              as VALIDATION_RESULT ( ROW_VALID )  --example: 1/0 true/false Boolean returned

left join   POST_TODAYS_ORDER_BATCH ( ORDER_NUMBER, [either pass the remainder of the order columns or fetch them in the function] )
              as POST_RESULT ( ROW_POSTED )  --example: 1/0 true/false Boolean returned
      on    ROW_VALIDATED = '1'

left join   DATA_WAREHOUSE_TODAYS_ORDER_BATCH ( ORDER_NUMBER, [either pass the remainder of the order columns or fetch them in the function] )
              as WAREHOUSE_RESULT ( ROW_WAREHOUSED )  --example: 1/0 true/false Boolean returned
      on    ROW_POSTED = '1'

where       coalesce( ROW_VALID,      '0' ) = '0'   --Capture only exceptions and unprocessed work.  
      or    coalesce( ROW_POSTED,     '0' ) = '0'   --Or, you can flip the logic to capture only successful rows.
      or    coalesce( ROW_WAREHOUSED, '0' ) = '0'

) with data
Run Code Online (Sandbox Code Playgroud)
  1. 如果表TODAYS_ORDER_BATCH包含1,000,000行,则VALIDATE_TODAYS_ORDER_BATCH将被调用1,000,000次,每行一次.
  2. 如果900,000行在VALIDATE_TODAYS_ORDER_BATCH内通过验证,则POST_TODAYS_ORDER_BATCH将被调用900,000次.
  3. 如果只有850,000行成功发布,则VALIDATE_TODAYS_ORDER_BATCH需要一些漏洞关闭LOL,而DATA_WAREHOUSE_TODAYS_ORDER_BATCH将被调用850,000次.
  4. 如果850,000行成功进入数据仓库(即没有生成其他异常),则表TODAYS_ORDER_PROCESSING_EXCEPTIONS将填充1,000,000 - 850,000 = 150,000个异常行.

此示例中的表函数调用仅返回单个列,但它们可能返回许多列.例如,验证订单行的表函数可以返回订单验证失败的原因.

在这种设计中,由于HLL请求者要求数据库在一个请求中处理整个批处理,因此几乎消除了HLL和数据库之间的所有聊天.这样可以减少数百万次对数据库的SQL请求,从而大量删除数百万个HLL过程或方法调用,从而提供了巨大的运行时改进.相比之下,通常一次处理单行的遗留代码通常会发送1,000,000个获取SQL请求,TODAYS_ORDER_BATCH中每行1个,加上至少1,000,000个HLL和/或SQL请求以进行验证,加上至少1,000,000 HLL和/或用于发布目的的SQL请求,以及用于将订单发送到数据仓库的1,000,000个HLL和/或SQL请求.当然,使用这个表函数设计,在表函数内部将SQL请求发送到数据库,但是当数据库向自身发出请求时(即从表函数内部),SQL请求的服务速度要快得多(特别是与HLL请求者正在从远程系统进行单行处理的遗留场景,最糟糕的情况是通过WAN - OMG请不要这样做.

如果使用表函数"获取结果集"然后将该结果集连接到其他表,则很容易遇到性能问题.在这种情况下,SQL优化器无法预测将从表函数返回的行集,因此无法优化到后续表的连接.出于这个原因,我很少使用它们来获取结果集,除非我知道结果集将是非常少量的行,因此不会导致性能问题,或者我不需要连接到后续表.

在我看来,表函数未充分利用的一个原因是它们通常被认为只是一个获取结果集的工具,结果集通常表现不佳,所以它们被作为一个"差"工具使用而被注销.

表函数对于将更多功能推送到服务器,消除数据库服务器与远程系统上的程序之间的大部分聊天,甚至消除数据库服务器与同一服务器上的外部程序之间的聊天非常有用.即使是同一台服务器上的程序之间的聊天也比许多人意识到的要多得多,而且大部分内容都是不必要的.表函数的强大之处在于使用它们来执行结果集处理中的操作.

有更多高级设计模式可以使用基于上述模式的表函数,您可以在这里进一步优化结果集处理,但是这篇文章对于大多数人来说已经很多了.