三种查询语言比较:SQL、SPL、PromQL
SQL、SPL、PromQL 是三种定位和用途差异显著的查询语言,分别聚焦于通用关系型数据查询、复杂多源数据处理和时序监控数据分析。以下从核心定位、设计目标、语法特点、典型场景等维度进行对比,并结合实例说明。
一、差异总览
维度 | SQL (Structured Query Language) | SPL (Structured Process Language) | PromQL (Prometheus Query Language) |
---|---|---|---|
核心定位 | 通用关系型数据库查询语言 | 多源复杂数据处理的领域特定语言(DSL) | 时序监控数据(指标)查询与分析的 DSL |
设计目标 | 简洁高效地操作关系型表数据(增删改查、聚合关联) | 解决 SQL 难以处理的复杂逻辑(分步计算、多源融合、非结构化) | 快速筛选、聚合、计算时序指标(随时间变化的监控数据) |
数据模型 | 关系模型(二维表,行/列结构) | 灵活模型(支持表、集合、JSON、文件等多源数据) | 时序模型(指标名 + 标签 + 时间戳 + 数值,如 metric{l=v} @ t = val ) |
语法风格 | 声明式(只说“查什么”,不说“怎么查”,引擎优化执行计划) | 过程式+声明式(分步描述“先做什么、再做什么”) | 声明式(针对时序场景优化,支持即时查询与聚合) |
典型应用场景 | 业务系统数据查询(订单统计、用户画像)、报表生成 | 大数据预处理、复杂漏斗分析、多源数据融合(如日志+数据库) | 监控指标查询(CPU 使用率、QPS)、告警规则配置、趋势图表 |
依赖环境 | 关系型数据库(MySQL、PostgreSQL、Oracle) | 支持 SPL 的工具/平台(如 esProc SPL、部分大数据平台) | Prometheus 监控系统、Grafana 可视化工具 |
二、分语言详解与实例
1. SQL:通用关系型数据的“标准查询语言”
SQL 是操作关系型数据库的工业标准,语法简洁直观,专注于“表与表之间的关系运算”,适合处理结构化的二维表数据。
- 基于“关系代数”,支持
SELECT
(查询)、JOIN
(关联)、GROUP BY
(聚合)、WHERE
(过滤)等核心语法; - 声明式语法:用户只需描述“想要的结果”,数据库引擎自动优化执行顺序(如选择索引、调整 JOIN 顺序);
- 不擅长:分步的复杂逻辑(如多轮计算依赖)、非结构化数据(如 JSON/日志)、多源异构数据融合。
假设有两张表:
orders
(订单表):order_id
(订单ID)、user_id
(用户ID)、order_time
(下单时间)、amount
(金额)users
(用户表):user_id
(用户ID)、city
(城市)
需求:查询 2024 年 1 月“北京”用户的订单总数和总金额。
SELECT COUNT(o.order_id) AS order_count,SUM(o.amount) AS total_amount
FROM orders o
JOIN users u ON o.user_id = u.user_id -- 关联两张表
WHERE u.city = '北京' AND o.order_time BETWEEN '2024-01-01' AND '2024-01-31';
2. SPL:复杂数据处理的“瑞士军刀”
SPL 是专为复杂数据处理设计的 DSL,兼顾过程式和声明式的优势,擅长解决 SQL 难以落地的场景(如多步依赖、非结构化数据、多源融合)。它的核心是“分步计算”——将复杂逻辑拆分为多个简单步骤,每个步骤的结果可被后续步骤直接复用。
- 支持多源数据:直接处理数据库表、CSV/Excel 文件、JSON 接口、日志等异构数据;
- 过程式语法:用“分步赋值”描述计算流程,逻辑清晰,便于调试;
- 内置大量高阶函数:针对时序、文本、集合等场景优化(如滑动窗口、漏斗计算、JSON 解析);
- 弥补 SQL 短板:解决 SQL 中“循环/分支难实现”“多步计算嵌套深”“非结构化数据处理繁琐”等问题。
需求:从“MySQL 订单表”和“本地 CSV 物流表”中,统计 2024 年 1 月“下单→支付→发货”的三步漏斗转化率(注:支付时间需在下单后 24 小时内,发货时间需在支付后 48 小时内)。
SPL 分步实现(逻辑清晰,每步结果可复用):
// 步骤1:读取 MySQL 订单表,筛选 2024年1月的订单(含下单、支付时间)
orders = connect("mysql").query("SELECT order_id, user_id, order_time, pay_time FROM orders WHERE order_time BETWEEN '2024-01-01' AND '2024-01-31'");// 步骤2:读取本地 CSV 物流表,筛选对应订单的发货时间
logistics = csv("logistics_202401.csv").select(order_id in orders.order_id);// 步骤3:筛选“支付时间在下单后24小时内”的订单(第一步漏斗)
step1 = orders.count(); // 下单总数
step2 = orders.select(pay_time - order_time <= 86400000).count(); // 支付数(24h内)// 步骤4:关联物流表,筛选“发货时间在支付后48小时内”的订单(第三步漏斗)
step3_data = orders.join(order_id, logistics, order_id).select(ship_time - pay_time <= 172800000);
step3 = step3_data.count(); // 发货数(48h内)// 步骤5:计算转化率
conv1_2 = step2/step1; // 下单→支付转化率
conv2_3 = step3/step2; // 支付→发货转化率
return conv1_2, conv2_3;
对比 SQL:若用 SQL 实现,需嵌套多层 JOIN
和 WHERE
,逻辑晦涩且难以调试;SPL 分步拆解后,每步目的明确,可单独验证结果。
3. PromQL:时序监控数据的“专属分析工具”
PromQL 是 Prometheus 监控系统的默认查询语言,专为时序数据(随时间变化的指标,如 CPU 使用率、接口 QPS、内存占用等)设计。时序数据的核心是“指标名 + 标签 + 时间序列”,PromQL 围绕这一模型优化了筛选、聚合和计算能力。
- 基于“时序模型”:每个指标由
metric_name{label1=value1, label2=value2}
标识,对应一条/多条时间序列(timestamp → value); - 支持即时查询与范围查询:即时查询返回“当前最新值”,范围查询返回“一段时间内的所有值”;
- 内置时序函数:针对监控场景优化(如
rate()
计算变化率、sum()
按标签聚合、avg_over_time()
计算时间窗口平均值); - 不擅长:非时序数据处理、复杂业务逻辑关联。
假设有监控指标:
http_requests_total{method="GET", status="200", instance="web-01"}
:web-01 实例的 GET 请求 200 状态码总数(计数器类型);node_cpu_seconds_total{mode="idle", instance="web-01"}
:web-01 实例的 CPU 空闲时间(计数器类型)。
实例1:计算 QPS(每秒请求数)
rate(http_requests_total{method="GET", status="200"}[5m])
- 含义:筛选所有实例的 GET 200 请求指标,计算过去 5 分钟内的平均每秒请求数(
rate()
函数用于计数器类型指标的变化率计算); - 应用:Grafana 绘制 QPS 趋势图。
实例2:计算 CPU 使用率
1 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance)
- 含义:
rate(...)
:计算每个实例过去 5 分钟的 CPU 空闲时间变化率;avg(...) by (instance)
:按实例聚合空闲率;1 - ...
:得到 CPU 使用率(1 - 空闲率);
- 应用:配置告警规则(如“CPU 使用率连续 3 分钟 > 80% 则告警”)。
实例3:筛选特定实例的指标
http_requests_total{instance=~"web-0.*", status!="500"}
- 含义:筛选实例名匹配
web-0*
(正则=~
)、状态码不是 500(!=
)的所有 HTTP 请求指标; - 应用:定位特定服务器的非 500 错误请求。
三、如何选择?
- 选 SQL:若处理的是关系型数据库中的结构化数据,需求是简单的查询、关联、聚合(如业务报表、订单统计),SQL 是最通用、最高效的选择。
- 选 SPL:若面临复杂数据处理场景(如多源数据融合、分步依赖的计算、非结构化数据处理、漏斗/留存分析),SQL 实现困难时,SPL 是更优解。
- 选 PromQL:若需分析监控系统的时序指标(如 CPU 使用率、QPS、告警规则配置),PromQL 是专为该场景设计的“原生语言”,无法被 SQL/SPL 替代。
三者并非互斥关系:实际业务中,常出现“SPL 处理多源数据后写入数据库,SQL 查询基础报表,PromQL 监控系统性能”的组合使用场景。
附一:SPL语法
SPL(Structured Process Language)是一种面向复杂数据处理的过程式领域特定语言(DSL),语法设计强调“分步计算”和“多源数据融合”,擅长将复杂逻辑拆分为简单步骤。其语法灵活,支持表、集合、JSON、文件等多源数据,同时提供丰富的内置函数处理时序、文本、聚合等场景。以下是SPL的核心语法规则与示例(以主流实现 esProc SPL 为例)。
基础概念:数据类型与变量
SPL支持多种数据类型,核心包括:
- 标量:数值(
123
)、字符串("abc"
)、日期(date("2024-01-01")
)、布尔值(true
/false
)。 - 集合:数组(
[1,2,3]
)、记录({id:1, name:"a"}
,类似JSON对象)。 - 表格:二维结构化数据(类似SQL的表,由行和列组成,是SPL最常用的类型)。
变量赋值:用 =
定义变量,变量名区分大小写,可存储任意类型数据:
num = 100 // 数值变量
name = "SPL" // 字符串变量
dates = [date("2024-01-01"), date("2024-01-02")] // 日期数组
orders = table([1,2], ["id","amount"]) // 表格变量(2行2列)
数据来源:读取多源数据
SPL的核心优势之一是直接处理多源异构数据,无需预先统一格式。常见数据源读取语法:
数据源 | 语法示例 | 说明 |
---|---|---|
数据库表 | db_orders = connect("mysql").query("SELECT * FROM orders") | 连接MySQL,查询orders表到变量db_orders |
CSV文件 | csv_orders = csv("D:/data/orders.csv") | 读取本地CSV文件 |
JSON文件 | json_data = json("D:/data/users.json") | 读取JSON文件(支持数组/对象) |
内存构造 | mem_table = table([1,2,3], ["id"], [{"name":"a"}, {"name":"b"}, {"name":"c"}]) | 手动构造内存表(列名+行数据) |
核心操作:表格处理(类似SQL,但更灵活)
SPL对表格的操作语法接近自然语言,支持筛选、排序、分组、关联等,且结果可直接赋值给变量(分步计算的核心)。
1. 筛选(where/select)
保留符合条件的行,语法:表.select(条件)
或 表.where(条件)
(两者等价)。
// 示例:从订单表中筛选金额>1000且城市为北京的订单
orders = connect("mysql").query("SELECT * FROM orders") // 假设orders表含amount、city列
filter_orders = orders.select(amount > 1000 && city == "北京")
2. 排序(sort)
按指定列排序,语法:表.sort(列名 [, 方向])
,方向默认升序(asc
),降序用 desc
。
// 示例:按金额降序排序,若金额相同则按时间升序
sorted_orders = orders.sort(amount desc, order_time asc)
3. 分组与聚合(group by)
按列分组并计算聚合值,语法:表.group(分组列; 聚合函数(列) as 别名...)
。
// 示例:按城市分组,计算每个城市的订单总数和平均金额
city_stats = orders.group(city; count(id) as total_orders, avg(amount) as avg_amount)
4. 关联(join)
关联多个表(类似SQL的JOIN),语法:表1.join(关联列, 表2, 关联列; 保留列...)
。
// 示例:关联订单表(orders)和用户表(users),保留订单ID、用户名、金额
users = connect("mysql").query("SELECT user_id, name FROM users")
joined = orders.join(user_id, users, user_id; orders.id, users.name, orders.amount)
5. 新增/修改列(derive/update)
derive
:新增列(不修改原表);update
:修改现有列(修改原表)。
// 示例1:新增“折扣后金额”列(原金额*0.9)
orders_with_discount = orders.derive(amount * 0.9 as discount_amount)// 示例2:将金额为空的行修改为0
orders.update(amount == null, amount:0) // 条件:amount为空;修改:amount=0
过程式计算:分步拆解复杂逻辑
SPL的核心设计理念是分步计算:将复杂逻辑拆分为多个简单步骤,用变量存储中间结果,后续步骤直接复用。这比SQL的嵌套子查询更直观。
示例:计算“连续3天活跃的用户数”
步骤拆解:
- 读取用户活跃记录(含user_id、active_date);
- 按用户分组,获取每个用户的活跃日期并排序;
- 计算相邻日期的差值,判断是否有连续3天及以上;
- 统计符合条件的用户数。
// 步骤1:读取数据
active_logs = csv("active_logs.csv") // 字段:user_id, active_date(日期类型)// 步骤2:按用户分组,获取排序后的活跃日期列表
user_dates = active_logs.group(user_id; ~.sort(active_date).active_date as dates)
// ~ 代表当前分组的子表,.active_date 提取该列转为数组// 步骤3:判断每个用户是否有连续3天活跃
qualified_users = user_dates.select(// 遍历日期数组,计算相邻日期差(天)for(i=1; i<dates.len(); i++){if(dates(i) - dates(i-1) == 1) { // 相邻日期差1天(连续)连续天数 += 1if(连续天数 >= 2) return true // 连续3天即满足(差2次1天)} else {连续天数 = 0}}return false
)// 步骤4:统计用户数
result = qualified_users.count()
对比SQL:用SQL实现需多层窗口函数嵌套(LAG
/LEAD
),逻辑晦涩;SPL分步拆解后,每步目的清晰,便于调试。
函数与运算符
SPL提供丰富的内置函数,覆盖数据类型转换、时间计算、文本处理、集合操作等场景,运算符与主流语言类似。
1. 常用函数
类别 | 函数示例 | 说明 |
---|---|---|
时间处理 | date("2024-01-01") | 字符串转日期 |
daysBetween(date1, date2) | 计算两个日期的天数差 | |
文本处理 | s.substr(1,3) | 字符串截取(从索引1开始,取3个字符) |
s.replace("a", "b") | 字符串替换 | |
集合操作 | array.len() | 数组长度 |
array.find(x) | 查找元素x在数组中的位置 | |
聚合函数 | sum(列) 、avg(列) 、max(列) | 求和、平均、最大值(同SQL) |
2. 运算符
- 算术:
+
、-
、*
、/
、%
(取模); - 比较:
==
、!=
、>
、<
、>=
、<=
; - 逻辑:
&&
(与)、||
(或)、!
(非); - 集合:
in
(元素是否在集合中),如x in [1,2,3]
。
流程控制:分支与循环
SPL支持过程式语言的流程控制语句,进一步增强复杂逻辑处理能力。
1. 分支(if-else)
// 示例:根据订单金额判断等级
order = orders.select(id==100).fetch(1) // 获取ID=100的订单
if(order.amount > 10000) {level = "VIP"
} else if(order.amount > 5000) {level = "Premium"
} else {level = "Normal"
}
2. 循环(for/while)
// 示例:计算1-100的和
sum = 0
for(i=1; i<=100; i++){sum += i
}
return sum // 结果:5050
多源数据融合
SPL天然支持多源数据直接关联,无需预先导入同一数据库。
示例:关联MySQL订单表与本地CSV物流表
// 步骤1:读取MySQL订单表(含order_id, user_id, amount)
orders = connect("mysql").query("SELECT * FROM orders WHERE order_time > '2024-01-01'")// 步骤2:读取本地物流CSV表(含order_id, ship_time)
logistics = csv("D:/logistics.csv").select(ship_time != null) // 筛选已发货的物流记录// 步骤3:关联两表,计算“订单金额>5000的平均发货时长(小时)”
joined = orders.join(order_id, logistics, order_id) // 按order_id关联
result = joined.select(amount > 5000).avg(hoursBetween(order_time, ship_time))
SPL特别适合大数据预处理、多源融合分析、复杂业务指标计算等场景,是对SQL的有效补充。
附二:PromQL语法
PromQL(Prometheus Query Language)是专为时序数据设计的查询语言,语法简洁但功能强大,核心用于从Prometheus中查询、聚合和分析指标数据。以下是其核心语法规则和结构:
数据模型:时序数据的基本单位
PromQL操作的核心是时间序列(Time Series),每条序列由三部分组成:
- 指标名(Metric Name):如
http_requests_total
(请求总数)、node_cpu_seconds_total
(CPU累计时间)。 - 标签(Labels):键值对,用于区分同一指标的不同维度(如
instance="server-01"
表示某台主机,status="200"
表示成功状态)。 - 样本(Samples):
(时间戳, 数值)
对,记录指标在不同时间点的取值。
基本查询:选择时间序列
通过“指标名+标签匹配”定位目标序列,是所有查询的基础。
1. 指标名匹配
# 匹配所有名为 http_requests_total 的时间序列
http_requests_total
2. 标签匹配(核心)
支持多种匹配模式,通过 {}
包裹标签条件:
操作符 | 含义 | 示例 | 说明 |
---|---|---|---|
= | 完全匹配 | http_requests_total{status="200"} | 匹配 status 为 “200” 的序列 |
!= | 不匹配 | http_requests_total{status!="500"} | 匹配 status 不是 “500” 的序列 |
=~ | 正则匹配(符合) | http_requests_total{status=~"2.."} | 匹配 status 以 “2” 开头的3位状态码(如200、201) |
!~ | 正则不匹配(不符合) | http_requests_total{instance!~"server-0.*"} | 排除 instance 以 “server-0” 开头的序列 |
3. 范围查询与瞬时查询
-
瞬时查询(Instant Vector):获取某个时间点的所有序列值(默认当前时间)。
示例:http_requests_total{instance="server-01"}
(当前 server-01 的请求总数)。 -
范围查询(Range Vector):获取一段时间窗口内的所有样本,格式为
[时间窗口]
。
示例:http_requests_total{instance="server-01"}[5m]
(过去5分钟 server-01 的请求总数时序)。
操作符:数值与集合运算
支持对时间序列进行数值计算或集合操作。
1. 算术操作符
支持 +
、-
、*
、/
、%
、^
,作用于序列的每个样本值:
# 计算内存使用率(已用内存 / 总内存 * 100%)
(node_memory_MemTotal_bytes - node_memory_MemFree_bytes) / node_memory_MemTotal_bytes * 100
2. 比较操作符
支持 ==
、!=
、>
、<
、>=
、<=
,返回符合条件的序列(值为1表示符合,0表示不符合):
# 筛选 CPU 使用率 > 80% 的主机
rate(node_cpu_seconds_total{mode!="idle"}[5m]) > 0.8
3. 逻辑/集合操作符
and
:交集(同时满足两个条件的序列)。or
:并集(满足任一条件的序列)。unless
:补集(满足第一个条件但不满足第二个的序列)。
# 匹配 status=200 且 instance 为 server-01 或 server-02 的请求
http_requests_total{status="200"} and http_requests_total{instance=~"server-0[12]"}
聚合操作:按标签维度聚合
通过聚合函数将多个序列合并为更少的序列,常用 by
或 without
指定聚合维度:
by (标签名)
:保留指定标签,按其分组聚合。without (标签名)
:排除指定标签,按剩余标签分组聚合。
# 1. 按 instance 分组,计算每个主机的总请求数
sum(http_requests_total) by (instance)# 2. 排除 instance 标签,计算所有主机的平均 CPU 使用率
avg(rate(node_cpu_seconds_total{mode!="idle"}[5m])) without (instance)
常用聚合函数:sum
(求和)、avg
(平均)、max
(最大)、min
(最小)、count
(计数)、topk(k, v)
(取前k大值)等。
函数:时序数据处理(核心能力)
PromQL提供大量内置函数处理时序数据,重点函数分类:
1. 速率与变化计算(用于counter/gauge类型)
rate(v range-vector)
:计算counter在时间窗口内的每秒增长率(适合长期趋势)。
示例:rate(http_requests_total[5m])
(过去5分钟的平均QPS)。irate(v range-vector)
:计算counter的瞬时增长率(适合短期波动)。
示例:irate(http_requests_total[1m])
(最近1分钟的瞬时QPS)。delta(v range-vector)
:计算gauge在时间窗口内的差值(如内存变化)。
示例:delta(node_memory_used_bytes[1h])
(1小时内内存使用变化量)。
2. 时间偏移(对比历史数据)
使用 offset
关键字偏移查询时间:
# 对比当前QPS与1小时前的QPS
rate(http_requests_total[5m]) / rate(http_requests_total[5m]) offset 1h
3. 预测与估算
predict_linear(v range-vector, t)
:线性预测t秒后的指标值(如磁盘是否将满)。
示例:predict_linear(node_filesystem_free_bytes{mountpoint="/"}[6h], 3600) < 0
(预测1小时后根目录是否无空间)。
PromQL的核心是“围绕时序数据的标签筛选、时间范围控制和聚合计算”,掌握标签匹配和常用函数(如 rate
、sum
)是关键,实际使用时需结合具体指标类型(counter/gauge/histogram)选择合适的操作。