使用 PostgreSQL 用户定义函数实现类似窗口函数的行为?
Posted
技术标签:
【中文标题】使用 PostgreSQL 用户定义函数实现类似窗口函数的行为?【英文标题】:Achieving window function-like behavior using a PostgreSQL user defined function? 【发布时间】:2020-03-01 04:03:52 【问题描述】:假设给定一个表observations_tbl
具有属性date
(天)和value
,我想生成新属性prev_day_value
以获取下表:
|---------------------|-------|----------------|
| date | value | prev_day_value |
|---------------------|-------|----------------|
| 01.01.2015 00:00:00 | 5 | 0 |
| 02.01.2015 00:00:00 | 4 | 5 |
| 03.01.2015 00:00:00 | 3 | 4 |
| 04.01.2015 00:00:00 | 2 | 3 |
|---------------------|-------|----------------|
我很清楚这样的输出通常可以使用WINDOW
函数获得。但是我如何通过 PostgreSQL 用户定义的函数来实现这一点?我想表明我处于必须使用函数的情况,如果不详细说明,很难解释为什么 - 这些是我的限制,如果有的话,这是一个技术挑战。
考虑这个模板查询:
SELECT *, lag(value,1) AS prev_day_value -- or lag(record,1) or lag(date,value,1) or lag(date,1) or lag(observations_tbl,1), etc.
FROM observations_tbl
我正在使用带有参数1
的函数lag
来查找在当前行之前1
的值 - 1
行的距离。我不在乎函数lag
可以有哪些其他参数(表名、其他属性)- 函数lag
看起来像什么来实现这样的功能?该函数可以是任何语言,SQL
、PL/pgSQL
甚至使用 PostgreSQL API/后端的C
。
我知道一个答案可以是在 lag
用户定义的函数中包装 WINDOW
查询。但我认为,如果我必须扫描整个表两次(一次在 lag
函数内,一次在函数外),那将是一项相当昂贵的操作。我在想,也许每条 PostgreSQL 记录都会有一个指向它以前可以直接访问的记录的指针?或者我可以以某种方式在这个特定的行/行号处打开一个游标,而不必扫描整个表?或者我的要求是不可能的?
【问题讨论】:
尝试使用用户定义的函数来复制内置函数的效率似乎——我应该说——具有挑战性。 你确定PostgreSQL没有办法解决这个要求吗? 。 .不,我不知道。您可以围绕内部定义编写一个包装器。但是这样做的需要有些不对劲。这似乎是一个 X-Y 问题,您关注的是错误的细节。 您可以将功能内联到您的存储过程中,直到找到更好的方法来定位您的数据:/ 如果您出于某些奇怪的原因“必须”使用函数,那么为什么不简单地将使用窗口函数的查询封装到 SQL 函数中呢? 【参考方案1】:您的请求无法使用关系工具来解决(窗口函数不是 SQL 中的关系扩展)。在 C 语言中,您可以编写自己的函数滞后替代方案。你可以用 PL8 语言(javascript)做同样的工作。不幸的是,PL/pgSQL 不存在窗口函数的 API。您不能编写简单的 PL/pgSQL 函数来访问与处理不同的行。
一种可能的替代方法(但存在一些性能风险)是编写表函数。您可以控制所有已处理的数据集,并且可以简单地执行此操作。
CREATE OR REPLACE FUNCTION report()
RETURNS TABLE(d date, v int, prev_v int) $$
DECLARE r RECORD;
BEGIN
prev_v := 0;
FOR r IN SELECT date, value FROM observations_tbl t ORDER BY 1
LOOP
d := r.date; v := r.value;
RETURN NEXT;
prev_v := v;
END LOOP;
END;
$$ LANGUAGE plpgsql;
没有其他可用的替代解决方案。在非常古老的年代,这些值是通过相关的自联接计算得出的,但这种解决方案的性能非常糟糕。
【讨论】:
“窗口函数不是 SQL 中的关系扩展” 到底是什么意思? 关系代数基于集合。集合内字段的顺序并不重要。您可以对这些集合进行一些操作 - 并集、乘法、子集......窗口函数基于不同的基础 - 顺序很重要。 关于窗口函数不是 SQL 的关系扩展的论点是完全错误的 - 请参阅 ISO SQL:2003 和 ISO SQL:2008。您说得对,Codd 博士 1970 年的原始论文(参见:Communications of the ACM,第 13 卷,第 6 期,377-387)没有包含它们,它是关于存储和检索的。进一步的窗口函数都是 Post Retrieval,所以你的关系代数就完成了。您是否还争辩说 ORDER BY 不是有效的 SQL 或其他正常操作。 @PavelStehule 在您的示例中,您指定了返回类型。我想知道是否有办法在函数体中指定返回类型? @Zeruno - 返回类型应该只在接口和计划时间定义。 Postgres 是类型严格的系统。【参考方案2】:什么Pavel posted,只是任务更少。应该更快:
CREATE OR REPLACE FUNCTION report()
RETURNS TABLE(d date, v int, prev_v int) AS
$func$
BEGIN
prev_v := 0;
FOR d, v IN
SELECT date, value FROM observations_tbl ORDER BY 1
LOOP
RETURN NEXT;
prev_v := v;
END LOOP;
END
$func$ LANGUAGE plpgsql;
如果它实际上将表上的多次扫描替换为单个扫描,则一般的想法是可以支付的。喜欢这里:
GROUP BY and aggregate sequential numeric values【讨论】:
以上是关于使用 PostgreSQL 用户定义函数实现类似窗口函数的行为?的主要内容,如果未能解决你的问题,请参考以下文章
ORACLE ---- 和PostgreSQL继承表类似的实现方式
在JS中自定义函数并实现类似 $.messager.confirm(title, msg, fn)功能,请附代码。