【问题标题】:How to pass a set of rows from one function into another?如何将一组行从一个函数传递到另一个函数?
【发布时间】:2014-11-25 07:29:22
【问题描述】:

概述

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

总结

将返回集合的函数的结果传递给另一个函数的正确方法是什么? (我注意到 create function ... xxs( setof x ) ... 会导致错误:ERROR: functions cannot accept set arguments,因此答案不会真正通过从一个函数到另一个函数的一组行。)

【问题讨论】:

  • 将返回集合的函数的结果传递给另一个函数的正确方法是什么?:没有,因为函数不接受结果集作为输入。可能导致不这样认为的是select xxs(xs()) 工作,但这是一个特例,一个黑客。它实际上调用xs() 一次,遍历行,为每一行调用xss(x)。如果要传递结果集,请使用游标。
  • @DanielVérité 我开始担心这种情况。我们可以:(i)从子查询中选择,例如select * from (select ...),这对我来说似乎很奇怪; (ii) 编写从查询返回结果集的函数,例如create function foo() ... select * from ...; (iii) 从此类函数的返回值中进行选择,例如,select * from foo();但不能 (iv) 再抽象出一个层次并做一些create function bar(X) ... select from X 的变体。
  • @DanielVérité 同时,我将研究游标,但这似乎是一个遗漏 [说该语言的新手 ;)] 因为在大多数语言中,如果您可以将表达式替换为调用以表达式为主体的函数时,您还希望能够将函数调用替换为保存先前调用该函数的结果的变量。但正如我所说,我在这里有点新人,所以我没有任何接近其他人的观点;可能有一些我还没有看到的很好的理由。
  • 您收到“function xxs(x) doesn't exist”的原因是因为数据库倾向于支持函数重载,在这种情况下,您可以拥有多个函数名称相同但每个函数名称不同的函数参数集。因此,在运行时,当它尝试查找您的函数时,它会同时使用函数名称和参数来查找函数的正确副本。函数 xxs(x) 显然存在,但不是一个接受结果集作为输入的函数。
  • @MikeJones xxs(x) 中的x 是参数的类型(因为x 是表的名称)。奇怪的是,虽然xs() 返回一个setof x,但系统正在寻找一个接受单个x 的函数。正如上面 DanielVerite 解释的那样,其原因是 xxs(xs()) 实际上是在调用 xs(),取回结果(一组 x)并尝试在每个结果上调用 xxs(...),这意味着需要 xxs(x)。

标签: sql postgresql


【解决方案1】:

表函数

我以执行非常高速、复杂的数据库迁移为生,使用 SQL 作为客户端和服务器语言(不使用其他语言),全部运行服务器端,代码很少从数据库引擎中出现。 表格函数在我的工作中发挥了巨大的作用。我不使用“游标”,因为它们太慢而无法满足我的性能要求,而且我所做的一切都是面向结果集的。表函数在完全消除游标的使用、实现非常高的速度方面为我提供了巨大的帮助,并为减少代码量和提高简单性做出了巨大贡献。

简而言之,您使用一个引用两个(或多个)表函数的查询,将数据从一个表函数传递到下一个表函数。 调用表函数的选择查询结果集充当将数据从一个表函数传递到下一个表函数的管道。 在我工作的 DB2 平台/版本上,它基于快速查看 9.1 Postgres 手册,那里也是如此,正如您所发现的,您只能将单行列值作为输入传递给任何表函数调用。 但是,由于表函数调用发生在查询的结果集处理过程中,因此您可以实现将整个结果集传递给每个表函数调用的相同效果,尽管在数据库引擎管道中,数据被传递每个表函数一次只能一行。

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

考虑这个例子(当我在 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
  1. 如果表 TODAYS_ORDER_BATCH 包含 1,000,000 行,则 VALIDATE_TODAYS_ORDER_BATCH 将被调用 1,000,000 次,一次 每一行。
  2. 如果 VALIDATE_TODAYS_ORDER_BATCH 中有 900,000 行通过验证,则 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 个 fetch 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 优化器无法预测将从表函数返回的行集,因此它无法优化与后续表的连接。出于这个原因,我很少使用它们来获取结果集,除非我知道结果集的行数非常少,因此不会导致性能问题,或者我不需要加入后续表。

在我看来,表函数未被充分利用的一个原因是,它们通常被认为只是一种获取结果集的工具,而结果集通常表现不佳,因此它们被认为是一种“差劲”的工具来使用。

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

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

【讨论】:

  • +1 因为这是一篇不错的文章。我很乐意使用多个结果函数并将它们连接起来,但我的目标是沿着函数 f 的行接受一组 X 行并基于它们返回一组 Y 行。 f 并不真正关心它获得了哪些 X 行,因此最好将那部分抽象出来并让它们传递给函数。这样我们就可以拥有f(best_Xes())f(worst_Xes())f(Xes_of_SpecialInterest()) 等。现在,我开始认为您提到的管道是不允许这样做的原因;为了把这种东西优化好,就是
  • 重要的是能够在适当的位置扩展函数的定义,因此优化器确实不希望在运行时到达一组行,而是希望他们可以扩展查询的调用。这是根本原因吗?
  • 我希望表函数可以接受整个结果集,尤其是动态结果集,作为您希望的输入。对于 DB2(我正在使用的版本)和 Postgres 9.1,情况并非如此。我不假装知道所有数据库。如果有支持该功能的数据库,那就是 ROCK STAR!为了处理你想要的,我认为创建数据库系统的工程师必须在运行时生成大量代码,并处理很多复杂性。最终结果将很难优化,因此在开发者社区中并不受欢迎。
猜你喜欢
  • 1970-01-01
  • 2020-09-26
  • 2017-03-12
  • 1970-01-01
  • 1970-01-01
  • 2016-09-18
  • 2021-02-16
  • 1970-01-01
相关资源
最近更新 更多