文档章节

PostgreSQL窗口函数分析

PGSmith
 PGSmith
发布于 09/05 16:51
字数 1027
阅读 736
收藏 3

今天看了一下PostgreSQL row_number的实现过程。之前一直好奇窗口函数是什么,原理是什么,今天稍稍解惑。下面就以row_number为例进行介绍: 窗口函数:

窗口函数在一组表行中执行计算,这些表行以某种方式与当前行相关。 这与使用聚合函数可以完成的计算类型相当。 但是,窗口函数不会导致行被分组到单个输出行,就像非窗口聚合调用一样。 相反,行保留其独立的身份。 在幕后,窗口功能不仅可以访问查询结果的当前行。

row_number使用示例:

[postgres@shawnpc bin]$ ./psql 
psql (13devel)
Type "help" for help.

postgres=# select row_number() over() as rownum, id from aa;
 rownum | id 
--------+----
      1 |  1
      2 |  2
      3 |  3
      4 |  4
      5 |  5
      6 |  6
      7 |  7
      8 |  8
      9 |  9
     10 | 10
(10 rows)

postgres=#

row_number代码:

/*
 * row_number
 * just increment up from 1 until current partition finishes.
 */
Datum
window_row_number(PG_FUNCTION_ARGS)
{
        WindowObject winobj = PG_WINDOW_OBJECT(); //获取窗口函数内存上下文
        int64           curpos = WinGetCurrentPosition(winobj); //初始化位置

        WinSetMarkPosition(winobj, curpos); //将行号和位置绑定
        PG_RETURN_INT64(curpos + 1); //返回行号
}

看起来似乎非常简单,但是经过调试发现这里和执行计划耦合度很高: 设置函数断点:

Breakpoint 1, window_row_number (fcinfo=0x7ffc158cce90) at windowfuncs.c:83
83	{
(gdb) bt
#0  window_row_number (fcinfo=0x7ffc158cce90) at windowfuncs.c:83
#1  0x0000000000632956 in eval_windowfunction (perfuncstate=0x1ca3768, result=0x1ca3738, isnull=0x1ca3750, winstate=0x1ca23e8, 
    winstate=0x1ca23e8) at nodeWindowAgg.c:1056
#2  0x0000000000635174 in ExecWindowAgg (pstate=0x1ca23e8) at nodeWindowAgg.c:2198
#3  0x0000000000605b82 in ExecProcNode (node=0x1ca23e8) at ../../../src/include/executor/executor.h:240
#4  ExecutePlan (execute_once=<optimized out>, dest=0x1c125e8, direction=<optimized out>, numberTuples=0, sendTuples=true, 
    operation=CMD_SELECT, use_parallel_mode=<optimized out>, planstate=0x1ca23e8, estate=0x1ca21c0) at execMain.c:1648
#5  standard_ExecutorRun (queryDesc=0x1c0eb70, direction=<optimized out>, count=0, execute_once=<optimized out>) at execMain.c:365
#6  0x000000000074c81b in PortalRunSelect (portal=portal@entry=0x1c52e90, forward=forward@entry=true, count=0, count@entry=9223372036854775807, 
    dest=dest@entry=0x1c125e8) at pquery.c:929
#7  0x000000000074db60 in PortalRun (portal=portal@entry=0x1c52e90, count=count@entry=9223372036854775807, isTopLevel=isTopLevel@entry=true, 
    run_once=run_once@entry=true, dest=dest@entry=0x1c125e8, altdest=altdest@entry=0x1c125e8, 
    completionTag=completionTag@entry=0x7ffc158cd7e0 "") at pquery.c:770
#8  0x0000000000749bc6 in exec_simple_query (query_string=0x1becfa0 "select row_number() over() as rownum, id from aa;") at postgres.c:1231
#9  0x000000000074aea2 in PostgresMain (argc=<optimized out>, argv=argv@entry=0x1c16f70, dbname=0x1c16e98 "postgres", username=<optimized out>)
    at postgres.c:4256
#10 0x000000000047e579 in BackendRun (port=<optimized out>, port=<optimized out>) at postmaster.c:4446
#11 BackendStartup (port=0x1c0ee70) at postmaster.c:4137
#12 ServerLoop () at postmaster.c:1704
#13 0x00000000006ddb9d in PostmasterMain (argc=argc@entry=3, argv=argv@entry=0x1be7bb0) at postmaster.c:1377
#14 0x000000000047f243 in main (argc=3, argv=0x1be7bb0) at main.c:210

从上可知,首先row_number函数执行是在执行计划执行之后进行调用的。 首先进入的是ExecutePlan:

static void
ExecutePlan(EState *estate,
                        PlanState *planstate,
                        bool use_parallel_mode,
                        CmdType operation,
                        bool sendTuples,
                        uint64 numberTuples,
                        ScanDirection direction,
                        DestReceiver *dest,
                        bool execute_once)
{
        TupleTableSlot *slot;
        uint64          current_tuple_count;
略

        for (;;)
        {
                /* Reset the per-output-tuple exprcontext */
                ResetPerTupleExprContext(estate);

                /*
                 * Execute the plan and obtain a tuple
                 */
                slot = ExecProcNode(planstate);

略
}

这里调用了ExecProcNode(宏定义,调用了ExecWindowAgg),ExecWindowAgg调用了eval_windowfunction,而正是eval_windowfunction完成了row_number的调用,并且构建了相关数据。通过调试可以发现,多少行数据就会调用多少次row_number。

eval_windowfunction:

/*
 * eval_windowfunction
 *
 * Arguments of window functions are not evaluated here, because a window
 * function can need random access to arbitrary rows in the partition.
 * The window function uses the special WinGetFuncArgInPartition and
 * WinGetFuncArgInFrame functions to evaluate the arguments for the rows
 * it wants.
 */
static void
eval_windowfunction(WindowAggState *winstate, WindowStatePerFunc perfuncstate,
                                        Datum *result, bool *isnull)
{
        LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
        MemoryContext oldContext;

        oldContext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_tuple_memory); //切换至tuple的内存上下文

        /*
         * We don't pass any normal arguments to a window function, but we do pass
         * it the number of arguments, in order to permit window function
         * implementations to support varying numbers of arguments.  The real info
         * goes through the WindowObject, which is passed via fcinfo->context.
         */
        InitFunctionCallInfoData(*fcinfo, &(perfuncstate->flinfo),
                                                         perfuncstate->numArguments,
                                                         perfuncstate->winCollation,
                                                         (void *) perfuncstate->winobj, NULL);//初始化fcinfo,为下面调用函数使用
        /* Just in case, make all the regular argument slots be null */
        for (int argno = 0; argno < perfuncstate->numArguments; argno++)
                fcinfo->args[argno].isnull = true;//见注释
        /* Window functions don't have a current aggregate context, either */
        winstate->curaggcontext = NULL;//见注释

        *result = FunctionCallInvoke(fcinfo);//调用函数
        *isnull = fcinfo->isnull;

        /*
         * Make sure pass-by-ref data is allocated in the appropriate context. (We
         * need this in case the function returns a pointer into some short-lived
         * tuple, as is entirely possible.)
         */
        if (!perfuncstate->resulttypeByVal && !fcinfo->isnull &&
                !MemoryContextContains(CurrentMemoryContext,
                                                           DatumGetPointer(*result)))
                *result = datumCopy(*result,
                                                        perfuncstate->resulttypeByVal,
                                                        perfuncstate->resulttypeLen);
     //见注释

        MemoryContextSwitchTo(oldContext); //切换回原上下文
}

 

至此分析结束。

© 著作权归作者所有

PGSmith

PGSmith

粉丝 95
博文 56
码字总数 60831
作品 0
济南
后端工程师
私信 提问
加载中

评论(3)

吾运昌隆
加油
吾运昌隆
所以说明了什么?
PGSmith
PGSmith 博主
这是算是一个笔记吧,后期会再进行整理的。窗口函数目前看来是和执行计划绑定的。在这里其实我想实现Oracle的rownumber,所以在这里分析一下,看如何实现PG上的rownumber。
阿里云在线数据仓库服务 AnalyticDB for PostgreSQL 典型场景解析

云数据库AnalyticDB for PostgreSQL(原HybridDB forPostgreSQL)是一种大规模并行处理(MPP)数据仓库服务,可提供海量数据在线分析服务。 AnalyticDB for PostgreSQL基于PostgreSQL内核构建...

陆封
05/26
0
0
PostgreSQL Oracle 兼容性 - connect by 2

标签 PostgreSQL , Oracle , 树形查询 , 递归 , connect by , tablefunc , connectby 背景 Oracle connect by语法经常用于有树形关系的记录查询,PostgreSQL使用CTE递归语法,可以实现同样的...

德哥
2018/10/05
0
0
数据库案例集锦 - 开发者的《如来神掌》

标签 PostgreSQL , PG DBA cookbook , PG Oracle兼容性 , PG 架构师 cookbook , PG 开发者 cookbook , PG 应用案例 背景 「剑魔独孤求败,纵横江湖三十馀载,杀尽仇寇,败尽英雄,天下更无抗...

德哥
2017/06/09
0
0
PostgreSQL Oracle 兼容性 - Analysis函数之keep

标签 PostgreSQL , Oracle , 分析函数 , 窗口函数 , keep 背景 Oracle 分析函数KEEP,类似OVER的语法结构(当然,含义与之不同)。keep可以用于普通的查询,也可以用于分组聚合,同时亦可用于...

德哥
2018/06/21
0
0
PostgreSQL 生成空间热力图

标签 PostgreSQL , 热力图 , 空间切割 , 并行计算 , parallel safe 背景 结合空间数据,计算基于地理位置信息的热力图,在空间数据可视化场景中是一个非常常见的需求。 结合流计算,可以实现...

德哥
2018/10/05
0
0

没有更多内容

加载失败,请刷新页面

加载更多

还为PDF转Word抓狂?以下神器让你在职场倍受欢迎!

身在职场的你,是否一直在琢磨:如何能让自己在公司更受欢迎?如何才能在办公室里混个好人缘?如何在同事圈里留个好印象?其实,想要让自己成为受欢迎的人,只要让自己成为大家需要的人不就行...

foxit2world
30分钟前
7
0
AndServer+Service打造Android服务器实现so文件调用

so 文件调用 随着 Android 移动安全的高速发展,不管是为了执行效率还是程序的安全性等,关键代码下沉 native 层已成为基本操作。 native 层的开发就是通指的 JNI/NDK 开发,通过 JNI 可以实...

夜幕NightTeam
32分钟前
4
0
Docker下kafka学习三部曲之二:本地环境搭建

在上一章《 Docker下kafka学习,三部曲之一:极速体验kafka》中我们快速体验了kafka的消息分发和订阅功能,但是对环境搭建的印象仅仅是执行了几个命令和脚本,本章我们通过实战来学习如何编写...

程序员欣宸
32分钟前
4
0
萌新推荐!不再为Excel转换PDF发愁,Aspose.Cells for .NET一步到位!

Aspose.Cells for .NET(点击下载)是Excel电子表格编程API,可加快电子表格管理和处理任务,支持构建具有生成,修改,转换,呈现和打印电子表格功能的跨平台应用程序。 将Excel工作簿转换为...

mnrssj
33分钟前
6
0
对于绘画小白怎么画制服?该注意什么?

怎样制作学生服装?想必绘画初学者们常常会想的问题吧,不知道怎样才能画好人物的衣服,别着急,今日就在这儿讲一些关于如何绘画学生衣服校服的教程给我们!期望能够帮到你们! 轻便西装是不...

热爱画画的我
38分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部