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

时间:2014-11-14 19:54:11

标签: sql postgresql

概述

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

select * from (select * from foo ...) 

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

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

select * from foos()

有没有办法将一个级别抽象得更远,以便能够做这样的事情(我知道函数实际上不能有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())

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

我正在使用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
$$;

当我加载此代码时,我得到了表定义的预期输出,我可以调用并按照我的预期从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.

我对“函数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
$$;
create or replace function xxs( x )
returns setof x
language sql as $$
  select * from x
         where x.id in ($1.id)
$$;
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

摘要

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

1 个答案:

答案 0 :(得分: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手册,你似乎省略了&#34 ; 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
  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个异常行。
  5. 此示例中的表函数调用仅返回单个列,但它们可能返回许多列。例如,验证订单行的表函数可以返回订单验证失败的原因。

    在此设计中,由于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优化器无法预测将从表函数返回的行集,因此它无法优化对后续表的连接。出于这个原因,我很少使用它们来获取结果集,除非我知道结果集将是非常少量的行,因此不会导致性能问题,或者我不需要加入后续表。

    在我看来,表函数未得到充分利用的一个原因是它们通常被认为只是一个获取结果集的工具,结果集通常表现不佳,所以它们被作为“穷人”#34;工具使用。

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

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