写点什么

Presto 设计与实现(十二):SQL 逻辑计划

作者:冰心的小屋
  • 2023-08-30
    中国香港
  • 本文字数:1865 字

    阅读完需:约 6 分钟

Presto 设计与实现(十二):SQL 逻辑计划

1. 模拟 SQL 实现

之前通过下面的例子介绍了 SQL 语句从词法分析 -> 语法分析 -> 抽象语法树 AST 的过程:

我们稍微将 SQL 变得复杂一些,需求描述如下:

  • 统计结果: 获取用户平均年龄最大的 10 个城市,输出城市名称、平均年龄、最大年龄和最小年龄;

  • 限定条件 1 :用户年龄大于等于 18;

  • 限定条件 2 :平均年龄大于 30。


通过使用过滤条件、分组相关函数、排序和限定条数,查询的 SQL 如下:

SELECT city, AVG(age) avg_age, MAX(age) max_age, MIN(age) min_age  FROM mysql.ice.user WHERE age >= 18 GROUP BY cityHAVING AVG(age) > 30 ORDER BY avg_age DESC LIMIT 10;
复制代码


假设用户数据已经存储在 List 集合中,想实现类似 SQL 查询的效果,我们需要对集合进行过滤处理:

package com.ice.user;
import lombok.Data;
import java.util.*;
// 用户信息@Datapublic class User { private int id; private String name; private String city; private int age;}
// 生成用户数据class UserStorage { public static List<User> users(int size) { return new ArrayList<>(); }}
class UserService{ public static void main(String[] args) { // 1. 所有用户数据 List<User> users = UserStorage.users(100);
// 2. 年龄有效范围 >= 18 Map<String, List<Integer>> userMap = filterAge(users, 18);
// 3. 获取平均年龄最大的 10 个城市,并且平均年龄 > 30, Map<String, Double> avgAgeMap = filterAvgAge(userMap, 18, 10); // 4. 获取最大年龄 Map<String, Integer> maxAgeMap = filterMaxAge(userMap, avgAgeMap.keySet()); // 5. 获取最小年龄 Map<String, Integer> minAgeMap = filterMinAge(userMap, avgAgeMap.keySet()); }
private static Map<String, List<Integer>> filterAge(List<User> users, int minAge){ return new LinkedHashMap<>(); }
private static Map<String, Double> filterAvgAge(Map<String, List<Integer>> userMap, int minAvgAge, int size){ return new LinkedHashMap<>(); } private static Map<String, Integer> filterMaxAge(Map<String, List<Integer>> userMap, Set<String> cities){ return new LinkedHashMap<>(); }
private static Map<String, Integer> filterMinAge(Map<String, List<Integer>> userMap, Set<String> cities){ return new LinkedHashMap<>(); }}
复制代码

通过编码的方式模拟了 SQL 的查询,这就是 SQL 逻辑计划的主要思想,不过实际生成 SQL 逻辑计划的过程可能比这复杂的多。

2. 什么是逻辑计划

逻辑计划:通过对抽象语法树的遍历,将语法树上的 Node 节点转化成 1 个或多个有前后依赖关系的计划,节点遍历完毕即生成一个完整的计划链表,这就是逻辑计划。逻辑计划让 SQL 查询离数据库、表、列和数据更近了一步。


逻辑计划以抽象类 PlanNode 表示,具体的计划必须继承 PlanNode,这里面列举下和查询相关的计划:

  • TableScanNode:表的元数据信息,表和实际 connector 的绑定关系,表所有列的定义例如列名、类型、是否可空以及其他一些限定条件;

  • FilterNode:对应 WHERE 的过滤条件或者 HAVING 的过滤条件,过滤条件以抽象类 RowExpression 表示;

  • AggregationNode:分组的字段集合以及作用于列的分组函数;

  • SortNode:排序字段的名称和对应的排序标准;

  • LimitNode:返回的数据条数以及和列相关的表达式集合;

  • OutputNode:逻辑计划以 OutputNode 作为输出,同时也是生成的最后一个计划,定义了输出的列名和列相关的表达式集合;

  • ProjectNode:用于计划之间的衔接,和列相关表达式的溯源。

3. 逻辑计划生成过程

使用了上面的 SQL 生成了未经优化的逻辑计划,这里忽略了 ProjectNode:

4. 表达式类型

在 Presto 中表达式以抽象类 RowExpression 表示,RowExpression 有 6 种具体的实现:

  • VariableReferenceExpression:表达式的直接引用或更名;

  • ConstantExpression:和数值、字符串的有关的字面变量;

  • CallExpression:函数调用表达式,SQL 语句中涉及的所有函数,生成逻辑计划时创建该实例;

  • SpecialFormExpression:特殊形式表达式,条件表达式 CASE WHEN、IF、NULLIF、COALESCE、IN、AND 和 OR 等;

  • LambdaDefinitionExpression:lambda 表达式,对于某些函数 filter、map_filter、reduce 和 zip_with 等,lambda 表达式可作为参数传递:


发布于: 刚刚阅读数: 3
用户头像

分享技术上的点滴收获! 2013-08-06 加入

一杯咖啡,一首老歌,一段代码,欢迎做客冰屋,享受编码和技术带来的快乐! 欢迎关注公众号:冰心的小屋

评论

发布
暂无评论
Presto 设计与实现(十二):SQL 逻辑计划_数据湖_冰心的小屋_InfoQ写作社区