资讯动态

PRQL Aggregate 变换详解:语义、用法与 SQL 编译原理

发布时间:2026/9/24 13:38:26 来源:尧图企业网站定制
PRQL Aggregate 变换详解语义、用法与 SQL 编译原理【免费下载链接】prqlPRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement项目地址: https://gitcode.com/gh_mirrors/pr/prqlaggregate是 PRQL 中负责把多行汇总为一行的核心变换也是 SQL 中SELECT SUM(...)、GROUP BY分组聚合在 PRQL 里的标准替代。本文以官方参考文档 aggregate.md 为骨架结合标准库源码与集成测试快照讲清aggregate的调用语法、内置聚合函数清单、与group的配合方式以及聚合函数必须显式放进aggregate这条与 SQL 截然不同的设计约定帮助你写出可编译、可复现的 PRQL 聚合查询。一、aggregate是什么在 PRQL 中aggregate将许多行汇总为一行不加group时它对整张表产生一行结果放在group管道内时它对每个分组各产生一行结果。它的调用形式是aggregate {expression or assign operations}花括号内部可以是一个或多个表达式也可以是用命名结果的赋值操作。从标准库源码 std.prql 可以看到aggregate的底层签名let aggregate func columns tbl relation - relation internal aggregate它接收一个关系tbl和一组columns返回一个关系。internal aggregate表示该变换由编译器内部实现而不是通过其他 PRQL 函数组合而来。一个最简示例把整张employees表汇总成一行from employees aggregate { average salary, ct count salary }这段代码会对salary求平均、统计salary的条数并分别输出为average与ct两列。二、与group组合分组聚合aggregate最常见的搭配是group。group先按关键列把行划分成组再把管道内的变换应用到每个组上。将aggregate放进group就等价于 SQL 的GROUP BY 聚合函数from employees group {title, country} ( aggregate { average salary, ct count salary, } )这里按title与country两列分组对每个分组分别计算平均薪资和人数。group本身是个通用工具组内可以是任何管道而不只是聚合。比如找出每个部门入职最早的那名员工可以先写出整表的做法from employees sort join_date take 1再原样套进groupfrom employees group role ( sort join_date # taken from above take 1 )这体现了 group.md 中强调的设计思想group内的变换对每个组做与整表相同的操作。三、内置聚合函数清单文档给出目前所有已声明的聚合函数min、max、count、count_distinct、average、stddev、sum、all、any和concat_array并提示标准库仍在扩充中。这些声明都可以在标准库源码 std.prql 中找到对应实现## Aggregate functions # These return either a scalar when used within aggregate, or a column when used anywhere else. let min column array - internal std.min let max column array - internal std.max let sum column array - internal std.sum let average column array - internal std.average let stddev column array - internal std.stddev let all column array - bool internal std.all let any column array - bool internal std.any let concat_array column array - text internal std.concat_array # Counts number of items in the column. # Note that the count will include null values. let count column array - int internal count # Deprecated in favour of filtering input to the [std.count] function (not yet implemented). {deprecated} let count_distinct column array - internal std.count_distinct源码中的注释透露了几个关键细节count会包含null值参与计数count_distinct已被标记为deprecated官方建议未来改用先过滤再count的方式但这一替代目前尚未实现所以count_distinct现阶段仍可直接使用聚合函数在aggregate内返回标量而在其他变换中使用时则返回一列即窗口语义详见第五节。concat_array是 PRQL 特有的字符串聚合类似 SQL 的STRING_AGGall/any对应 SQL 的BOOL_AND/BOOL_OR。四、编译到 SQL从集成测试看真实输出aggregate的编译行为在集成测试中有大量快照佐证。仓库的 queries/aggregation.prql 把四种聚合函数组合在一个aggregate中from tracks filter genre_id 100 derive empty_name name aggregate {sum track_id, concat_array name, all empty_name, any empty_name}其编译快照 aggregation.snap 展示了最终的 SQLSELECT COALESCE(SUM(track_id), 0), COALESCE(STRING_AGG(name, ), ), COALESCE(BOOL_AND(name ), TRUE), COALESCE(BOOL_OR(name ), FALSE) FROM tracks WHERE genre_id 100可以看到sum→SUMconcat_array→STRING_AGGall→BOOL_ANDany→BOOL_OR编译器对聚合结果自动套上了COALESCE如SUM配0、BOOL_OR配FALSE避免空表聚合返回NULL导致下游出错前面的filter变成了WHERE整个查询只有一个SELECT没有任何GROUP BY因为这里没有使用group。再看分组聚合的例子 queries/group_all.prqlfrom aalbums take 10 join tracks (album_id) group {a.album_id, a.title} (aggregate price (sum tracks.unit_price | math.round 2)) sort album_id编译快照 group_all.snapWITH table_0 AS ( SELECT album_id, title FROM albums AS a LIMIT 10 ) SELECT table_0.album_id, table_0.title, ROUND(COALESCE(SUM(tracks.unit_price), 0), 2) AS price FROM table_0 INNER JOIN tracks ON table_0.album_id tracks.album_id GROUP BY table_0.album_id, table_0.title ORDER BY table_0.album_id这个例子展示了聚合结果的三种常见用法命名聚合结果price ...、在聚合列上继续做标量运算math.round 2对SUM结果取整、以及aggregate之后再接sort。SQL 端也验证了group的分组列正是GROUP BY的列且ROUND(COALESCE(...), 2)体现了嵌套运算的编译顺序。如果聚合后再做窗口运算、排序和lagqueries/invoice_totals.prql 给出了完整链路group {city, street} ( derive total ii.unit_price * ii.quantity aggregate { num_orders count_distinct i.invoice_id, num_tracks sum ii.quantity, total_price sum total, } )快照 invoice_totals.snap 中第一段groupaggregate被编译为带GROUP BY i.billing_city, i.billing_address的子查询随后的window expanding:true、lag 7则生成SUM(...) OVER (PARTITION BY ...)与LAG(...) OVER (...)说明 PRQL 能够把聚合阶段与窗口阶段编译进同一个 SQL 语句的不同层次。从编译器实现看Transform::Aggregate是 SQL 生成阶段的关键分界点gen_query.rs 使用break_up按Transform::Aggregate以及Union切分管道anchor.rs 则根据后续是否还有Aggregate决定Filter是编译为WHERE还是HAVING——这也正是aggregate之后的filter变成HAVING这一 SQL 语义的来源。五、Aggregate is required聚合不会自动触发这是aggregate变换最重要、也最反直觉的约定在 SQL 中只要SELECT里出现聚合函数整条语句就自动变成聚合查询而 PRQL 中在derive或select等任何非aggregate变换里使用聚合函数都不会触发聚合。例如from employees derive {avg_sal average salary}PRQL 会把average salary解释为窗口函数为每一行都计算出一个平均值列而不是把表压成一行。这样设计的目的在 transforms/README.md 中有明确说明变换强调正交性orthogonality即每个变换保持某些关系性质不变——select和derive不改动行数filter和take不改动列数。如果derive中的聚合函数悄悄改变行数这个不变量就被破坏了因此derive只允许增加一列行数永远不变。反观 SQLSELECT子句中出现聚合函数会让整个语句从行数不变跳变成每分组一行这种隐式语义切换正是 PRQL 想要消除的。要真正聚合必须显式使用aggregate变换。如果确实需要窗口语义可以直接使用作用于整表如derive {rnk rank age}配合group按分组计算如group department (sort age | derive {rnk rank age})或使用window变换精确控制行/值范围rows:、range:、expanding:、rolling:。窗口相关的完整语义与示例见 window.md标准库中rank、lag、lead、first、last、row_number等窗口函数也都在 std.prql 中声明。window变换的签名同样可以在这里查到默认rows:0..-1、expanding:false、rolling:0。六、实用技巧与注意事项1. 聚合列可以用|继续做标量运算聚合函数返回值可以在管道中继续变换例如group_all.prql里的sum tracks.unit_price | math.round 2等价于ROUND(SUM(...), 2)。2.count与count_distinct的取舍count包含null值count_distinct目前可用但已被标记为废弃。若想只统计非空值可先用filter过滤再count。3. 没有group时aggregate汇总整表此时生成不带GROUP BY的SELECT ... FROM ...如第一节的示例。若对空表执行COALESCE包装见 aggregation.snap会保证返回0/FALSE/空字符串等安全默认值而不是NULL。4. 聚合之后的操作aggregate之后可以继续sort、filter编译为HAVING、window、derive等PRQL 会按Aggregate切分 SQL 语句层次见 gen_query.rs。如果想要HAVING语义直接把filter写在aggregate之后即可。5. 运算符风格前缀还是中缀aggregate {sum track_id}与aggregate {n1 (track_id | sum)}在 queries/group_sort.prql 中均被使用两者等价——PRQL 支持把列用|管道传入聚合函数便于与其他变换连写。七、参考资料本文核心依据transforms/aggregate.md变换总览与正交性设计transforms/README.mdgroup变换transforms/group.md窗口语义与window变换transforms/window.md聚合函数与aggregate/group/window的标准库签名semantic/std.prql集成测试与编译快照queries/aggregation.prql、queries/group_all.prql、queries/invoice_totals.prqlSQL 生成阶段的Aggregate切分逻辑sql/gen_query.rs、sql/pq/anchor.rs【免费下载链接】prqlPRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement项目地址: https://gitcode.com/gh_mirrors/pr/prql创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价