大数据Doris之_9_数据查询
前言
Github:https://github.com/HealerJean
一、连接(Join)
在关系型数据库中,数据被分布在多个表中,这些表之间通过特定关系相互关联。
SQLJOIN操作允许我们根据这些关联条件将不同的表合并成一个更完整的结果集。
1、Doris 支持的 JOIN 类型
INNERJOIN(内连接):对左表每一行和右表所有行进行JOIN条件比较,返回两个表中满足JOIN条件的匹配行。详细信息请参考 [SELECT]中有关于联接查询的语法定义LEFTJOIN(左连接):在INNERJOIN的结果集基础上。如果左表的行在右表中没有匹配,则返回左表的所有行,同时右表对应的列显示为NULL。RIGHTJOIN(右连接):与LEFTJOIN相反,如果右表的行在左表中没有匹配,则返回右表的所有行,同时左表对应的列显示为NULL。FULLJOIN(全连接):在INNERJOIN的结果集基础上。返回两个表中所有的行,如果某行在另一侧表中没有匹配,则另一侧表的相应列显示为 NULL。CROSSJOIN(交叉连接):没有JOIN条件,返回两个表的笛卡尔积,即左表的每一行与右表的每一行都进行组合。LEFTSEMIJOIN(左半连接):对左表每一行和右表所有行进行JOIN条件比较,如果存在匹配,就返回左表的对应行。RIGHTSEMIJOIN(右半连接):与LEFTSEMIJOIN相反,对右表每一行和左表所有行进行JOIN条件比较,如果存在匹配,就返回右表的对应行。LEFTANTIJOIN(左反半连接):对左表每一行和右表所有行进行JOIN条件比较,如果没有匹配,则返回左表的对应行。RIGHTANTIJOIN(右反半连接):与LEFTANTIJOIN相反,对右表每一行和左表所有行进行JOIN条件比较,如果没有匹配,则返回这些行。NULLAWARELEFTANTIJOIN(对NULL值特殊处理的左反半连接):与LEFTANTIJOIN类似,但忽略左表中匹配列为NULL的行。
2、Doris 中的 JOIN 物理实现
Doris 支持两种 JOIN 的物理实现方式:Hash Join 和 Nest Loop Join。
HashJoin: 在右表上根据等值JOIN列构建一个哈希表,左表的数据以流式方式通过该哈希表进行JOIN计算。这种方法的局限性在于它仅适用于等值JOIN条件的情况。NestLoopJoin: 通过两层循环,以左表驱动,对左表的每一行逐一遍历右表的每一行,进行join条件判断。适用于所有JOIN场景,包括处理HashJoin无法胜任的情况,比如涉及大于或小于比较条件的查询,或是需要执行笛卡尔积运算的场景。但相比HashJoin,NestLoopJoin在性能上可能会有所不及。
3、Doris Hash Join 的实现方式
作为分布式
MPP数据库,ApacheDoris在HashJoin过程中需要进行数据的Shuffle,进行拆分调度,以确保JOIN结果的正确性。以下是几种数据Shuffle方式:
说明:Hash Join 的实现方式选择是由 Doris 底层自动决定的,而非调用方直接指定。 Doris 在生成分布式执行计划时,会根据表的数据分布、大小以及查询条件等因素,自动选择最优的 Join 方式,包括 Hash Join、Nest Loop Join、Broadcast Join、Shuffle Join、Bucket Shuffle Join 和 Colocate Join 等。
| 对比维度 | Broadcast Join |
Partition Shuffle Join |
Bucket Shuffle Join |
Colocate Join |
|---|---|---|---|---|
| 基本原理 | 将小表广播到所有节点,本地连接。 | 按连接键重新分区(Shuffle),本地连接。 |
预先按连接键分桶,减少或避免 Shuffle。 |
确保相同连接键的数据存储在同一节点,避免 Shuffle。 |
| 使用场景 | 小表 Join 大表,小表尺寸可控。 |
大表 Join 大表,无预分桶或分桶规则不一致。 |
大表 Join 大表,数据分布稳定,频繁连接。 |
频繁连接的大表,对延迟敏感,需避免 Shuffle。 |
| 主要限制 | 小表尺寸受限,内存压力大。 | 高网络开销,数据倾斜风险高。 | 分桶键需与连接键一致,分桶数需相同;维护成本高。 | 数据倾斜风险,维护复杂度高,灵活性受限。 |
| 网络开销 | 低(广播小表,开销与节点数相关)。 | 高(需按连接键重新分配数据)。 | 低(预分桶减少Shuffle,可能零Shuffle)。 |
极低(数据已同分布,通常零Shuffle)。 |
| 数据分布要求 | 无特殊要求。 | 无特殊要求,按连接键动态分配。 | 需预先分桶,分桶键与连接键一致,分桶数相同。 | 需确保相同连接键的数据存储在同一节点。 |
| 适用数据量 | 小表(通常<100MB)与大表。 | 任意规模,但大表间更常见。 | 大表,数据分布稳定。 | 大表,频繁连接。 |
| 延迟表现 | 低(避免Shuffle)。 | 中(Shuffle带来延迟)。 | 低(减少Shuffle)。 | 低(零Shuffle,本地连接)。 |
| 典型实现框架 | Spark SQL(自动广播)、Flink。 | Spark SQL、Hive、Flink(默认Shuffle)。 | Spark SQL(分桶表)、Hive(分桶表)。 | 分布式数据库(如Greenplum)、Apache Kudu。 |
| 优化建议 | 调整广播阈值,选择合适的小表。 | 减少 Shuffle 数据量,增加分区数,使用压缩。 |
确保分桶键与连接键一致,调整分桶数。 | 设计合理的分布策略,监控数据倾斜。 |
1)Broadcast Join
Broadcast Join(广播连接)是一种优化的大表与小表连接策略,在分布式计算框架(如
Spark、Flink)中广泛应用。其核心思想是将小表的数据广播到所有节点,使每个节点都能在本地完成连接操作,从而避免数据混洗(Shuffle)。

a、核心原理:
- 广播小表:将小表的数据从驱动节点发送到所有工作节点。
- 本地连接:每个工作节点使用本地的大表分区数据与广播的小表数据进行连接。
- 结果合并:各节点完成连接后,将结果合并或直接输出。
b、举例说明
-
大表
S(例如用户行为日志):+----+-------+ | id | value | +----+-------+ | 1 | A | | 2 | B | | 3 | C | +----+-------+ -
小表
R(例如用户信息,尺寸较小):+----+-------+ | id | info | +----+-------+ | 2 | X | | 3 | Y | +----+-------+
执行过程:
-
广播小表
R- 驱动节点将小表
R的数据广播到所有工作节点。
- 驱动节点将小表
-
本地连接
- 每个工作节点使用本地的大表分区数据与广播的
R表进行连接。 - 例如,节点1处理
S的分区数据(1, A)、(2, B),与广播的R表连接,输出(2, B, X)。 - 节点 2 处理
(3, C),与广播的R表连接,输出(3, C, Y)。
- 每个工作节点使用本地的大表分区数据与广播的
-
结果合并
-
各节点的连接结果合并为最终输出:
+----+-------+-----+ | id | value | info| +----+-------+-----+ | 2 | B | X | | 3 | C | Y | +----+-------+-----+
-
c、 使用场景
- 小表
Join大表:当其中一个表(通常是右表)尺寸较小,可以高效地广播到所有节点。 - 减少
Shuffle开销:避免因数据混洗带来的网络传输和I/O开销。 - 实时或低延迟需求:广播操作通常比
Shuffle更快,适合对延迟敏感的场景。
d、 限制
-
小表尺寸限制:小表必须足够小,能够高效地广播到所有节点。通常,框架对广播表的大小有限制(如Spark中通过
spark.sql.autoBroadcastJoinThreshold配置,默认约10MB)。 -
内存压力:广播表需要在每个节点的内存中保留一份副本,如果节点数量多或广播表较大,可能导致内存不足。
-
不适用于大表Join大表:当两个表都很大时,广播其中一个表会导致极高的内存和网络开销,此时应选择其他策略(如Shuffle Join)。
e、网络开销分析
- 驱动节点将小表数据发送到所有工作节点,网络开销为
小表大小 × (节点数 - 1)。 - 例如,小表大小为
10MB,集群有99个JOIN节点,则总开销为10MB × 99 ≈ 990MB。
2)Partition Shuffle Join
是一种分布式
Join操作,通过Shuffle阶段将左右表中相同连接键(如id)的数据发送到同一节点,然后在节点内完成Join。其核心原理如下:

a、核心原理
- 分区(
Partitioning)- 左表(
S)和右表(R)根据连接键(如id)计算Hash值,并分配到不同分区。 - 例如:
Hash(id) % 分区数决定数据去向。
- 左表(
Shuffle(数据重分布)- 系统将左右表中相同
id的数据通过网络发送到同一节点。 - 例如:
S.id=2和R.id=2会被路由到同一分区(如分区2)。
- 系统将左右表中相同
- 本地
Join(LocalJoin)- 在每个分区内,对左右表的数据进行本地
Join(如HashJoin或SortMergeJoin)
- 在每个分区内,对左右表的数据进行本地
b、举例说明
假设:
-
左表
S+----+-------+ | id | value | +----+-------+ | 1 | A | | 2 | B | | 3 | C | +----+-------+ -
右表
R:+----+-------+ | id | info | +----+-------+ | 2 | X | | 3 | Y | | 4 | Z | +----+-------+ -
分区数:
2(分区0和分区1)。
执行过程:
- 分区计算
Hash(id)%2S.id=1→1 % 2 = 1(分区1)。S.id=2→2 % 2 = 0(分区0)。S.id=3→3 % 2 = 1(分区1)。R.id=2→2 % 2 = 0(分区0)。R.id=3→3 % 2 = 1(分区1)。R.id=4→4 % 2 = 0(分区0)。
Shuffle- 分区
0:S.id=2和R.id=2、R.id=4。 - 分区
1:S.id=1、S.id=3和R.id=3。
- 分区
- 本地
Join- 分区
0:S.id=2和R.id=2匹配,输出(2, B, X)。R.id=4无匹配,丢弃。
- 分区1:
S.id=3和R.id=3匹配,输出(3, C, Y)。S.id=1无匹配,丢弃。
- 分区
最终结果:
+----+-------+-----+
| id | value | info|
+----+-------+-----+
| 2 | B | X |
| 3 | C | Y |
+----+-------+-----+
c、使用场景
- 大表
Join大表:当左右表数据量都很大时,无法通过BroadcastJoin(广播小表)优化,必须通过Shuffle将数据重分布。 - 连接键分布均匀:如果连接键(如
id)的分布均匀,Shuffle后数据可以均匀分配到各节点,避免数据倾斜。 - 无预分区或
Bucket优化:如果表未预先按连接键分桶(Bucket),或分桶规则不一致,必须通过ShuffleJoin完成。
d、限制
- 高网络开销:
Shuffle阶段需要将大量数据通过网络传输,可能导致网络瓶颈。 - 数据倾斜风险:如果连接键分布不均匀(如某些
id出现次数过多),会导致部分分区数据量过大,影响性能。 - 内存压力:
Shuffle后的数据需要在内存中完成Join,如果数据量超过内存限制,可能触发磁盘溢出(Spill),显著降低性能。
e、网络开销
- 假设左右表各有
N条记录,且每条记录大小为S字节。 - 如果连接键分布均匀,每个分区接收的数据量约为
(N * S) / 分区数。 - 但如果数据倾斜,某些分区可能接收
O(N)的数据。
3)Bucket Shuffle Join
BucketShuffleJoin(分桶Shuffle连接)是一种优化的分布式连接策略,结合了 分桶(Bucket) 和Shuffle的思想。其核心在于预先将数据按连接键分桶存储,使得连接时相同连接键的数据位于同一或相关分桶中,从而减少Shuffle的数据量或完全避免Shuffle。

a、核心原理:
- 数据分桶(
Bucket):- 在数据写入时(如存储为表),根据连接键的哈希值将数据分配到固定数量的桶(Bucket)中。
- 例如,使用
id % 4将数据分配到 4 个桶中。
- 连接时匹配桶:
- 连接操作时,左右表相同桶号的数据会被发送到同一节点(或直接在本地连接,如果桶分布一致)。
- 由于数据已按连接键分桶,
Shuffle的数据量大大减少,甚至可以避免。
b、示例说明
假设:
- 左表
S(用户行为表)和 右表R(用户信息表)均按id分4个桶。 - 分桶规则:
bucket_id = id % 4。
数据分布:
-
表
S:Bucket 0: (id=4, value=D), (id=8, value=H) Bucket 1: (id=1, value=A), (id=5, value=E) Bucket 2: (id=2, value=B), (id=6, value=F) Bucket 3: (id=3, value=C), (id=7, value=G) -
表
R:Bucket 0: (id=4, info=W), (id=8, info=Z) Bucket 1: (id=1, info=X) Bucket 2: (id=2, info=Y), (id=6, info=V) Bucket 3: (id=3, info=U)
执行过程:
-
分桶存储:
- 表
S和R在存储时已按id % 4分桶,数据物理上存储在对应的桶文件中。
- 表
-
连接时桶匹配:
- 连接操作时,相同桶号的数据会被处理:
- Bucket 0:
S中的(4,D)、(8,H)与R中的(4,W)、(8,Z)连接。 - Bucket 1:
S中的(1,A)、(5,E)与R中的(1,X)连接(id=5无匹配,丢弃)。 - Bucket 2:
S中的(2,B)、(6,F)与R中的(2,Y)、(6,V)连接。 - Bucket 3:
S中的(3,C)、(7,G)与R中的(3,U)连接(id=7无匹配,丢弃)。
- Bucket 0:
- 连接操作时,相同桶号的数据会被处理:
-
结果合并:各桶的连接结果合并为最终输出:
+----+-------+-----+ | id | value | info| +----+-------+-----+ | 2 | B | Y | | 3 | C | U | | 4 | D | W | | 6 | F | V | | 8 | H | Z | +----+-------+-----+ (假设id=1无匹配info,id=5和id=7无匹配,不输出)
c、 使用场景
- 频繁连接的表:当两个表需要频繁进行连接操作时,预先分桶可以显著提升连接性能。
- 数据分布稳定:连接键的分布稳定,不会频繁变化,确保分桶的有效性。
- 减少
Shuffle开销:适用于大表连接,通过分桶减少Shuffle的数据量,甚至实现共分区(Co-partition)避免Shuffle。
d、限制
- 分桶键选择:分桶键必须与连接键一致,否则无法实现桶匹配。
- 分桶数一致:左右表的分桶数必须相同,否则可能导致数据分布不均。
- 分区规则不一定要求相同(但建议一致):分区规则不一致确实很可能导致即使分桶规则相同,数据也无法保证在同一个节点上,从而影响
BucketShuffleJoin的有效性 - 数据倾斜风险:如果连接键分布不均匀,某些桶的数据量可能过大,影响性能。
- 维护成本:预先分桶需要额外的存储和管理成本,不适合临时查询或动态数据。
e、 网络开销分析
- 分桶存储阶段数据在写入时已按桶分布,无额外网络开销。
- 连接阶段:
- 如果左右表的桶分布一致(如使用相同的分桶规则),则相同桶号的数据已在同一节点,无需
Shuffle。 - 如果桶分布不一致,可能需要少量
Shuffle调整数据位置。
- 如果左右表的桶分布一致(如使用相同的分桶规则),则相同桶号的数据已在同一节点,无需
4)Colocate Join
ColocateJoin(同分布连接)是一种优化的分布式连接策略,其核心思想是确保左右表中具有相同连接键的数据存储在同一个节点上,从而在连接时无需数据移动(即避免Shuffle),直接在本地完成连接操作。

a、核心原理:
- 数据分布一致性:在数据写入阶段,通过特定的分布策略(如哈希分布或范围分布),确保左右表中相同连接键的数据被路由到同一个节点。
- 本地连接:由于数据已经同分布,连接操作可以在每个节点上独立进行,无需跨节点传输数据。
- 结果合并:各节点完成本地连接后,将结果合并或直接输出。
为了使得 Table 能够有相同的数据分布,同一 CG Colocation Group(共址分组) 内的 Table 必须保证以下属性相同:
-
分桶列和分桶数
分桶列,即在建表语句中
DISTRIBUTED BY HASH(col1, col2, ...)中指定的列。分桶列决定了一张表的数据通过哪些列的值进行 Hash 划分到不同的 Tablet 中。同一CG内的 Table 必须保证分桶列的类型和数量完全一致,并且桶数一致,才能保证多张表的数据分片能够一一对应的进行分布控制。 -
副本数
同一个
CG内所有表的所有分区(Partition)的副本数必须一致。如果不一致,可能出现某一个Tablet的某一个副本,在同一个BE上没有其他的表分片的副本对应。
b、示例说明
假设:
- 左表
S(订单表)和 右表R(用户信息表),连接键为user_id。 - 分布策略:使用哈希分布,
hash(user_id) % 节点数,确保相同user_id的数据存储在同一个节点。
执行过程:
- 数据写入
- 左表
S和右表R在写入时,根据user_id的哈希值被分配到相同的节点。 - 例如,
user_id=1001的数据无论来自左表还是右表,都会被存储在节点2上。
- 左表
- 连接操作
- 每个节点使用本地的
S和R表数据,根据user_id进行连接。 - 节点2处理
user_id=1001的数据,输出连接结果。
- 每个节点使用本地的
- 结果合并:各节点的连接结果合并为最终输出。
c、使用场景
- 频繁连接的表:当两个表需要频繁进行连接操作时,预先确保数据同分布可以显著提升连接性能。
- 实时或低延迟需求:由于避免了
Shuffle,连接操作可以快速完成,适合对延迟敏感的场景。 - 大规模数据集:对于无法通过广播或简单分桶优化的大表连接,
ColocateJoin提供了一种有效的解决方案。
d、限制
- 数据倾斜风险:如果连接键分布不均匀,某些节点可能承担过多的数据,导致性能瓶颈。
- 维护成本:确保数据同分布需要额外的管理和维护工作,如重新分布数据以应对节点增减。
- 灵活性受限:一旦数据分布策略确定,调整起来可能较为复杂,不适合动态变化的场景。
e、网络开销分析
- 数据写入阶段:数据根据分布策略被路由到相应节点,可能产生一定的网络开销。
- 连接阶段:由于数据已经同分布,连接操作无需额外网络传输,网络开销极低。
二、聚合多维分析
ROLLUP、CUBE和GROUPING SETS是SQL中用于多维数据汇总的强大工具,它们能够简化复杂的聚合查询,适用于数据分析、报表生成等场景。以下是详细介绍和示例:
| 特性 | ROLLUP |
CUBE |
GROUPING SETS |
|---|---|---|---|
| 生成组合 | 层次化汇总(从细粒度到粗粒度)。 | 所有可能的维度组合。 | 用户指定的特定组合。 |
| 适用场景 | 逐级汇总(如地区→年份→总计)。 | 全面多维分析(如所有地区、年份组合)。 | 定制化汇总(如只统计某些组合)。 |
| 灵活性 | 较低(固定层次)。 | 较高(所有组合)。 | 最高(完全自定义)。 |
| 示例 | GROUP BY ROLLUP(region, year) |
GROUP BY CUBE(region, year) |
GROUP BY GROUPING SETS((region), (year)) |
GROUPING:适用于需要单独判断某一列是否被汇总的场景。例如,在报表中添加标识列,明确哪些维度被汇总。GROUPING_ID:适用于需要快速识别汇总行类型的场景。例如,在结果集中添加一个汇总标识列,方便后续处理(如排序、过滤等)。
1)聚合分析
a、ROLLUP
-
定义:
ROLLUP用于生成层次化的汇总数据,按照指定的列顺序从最细粒度逐步汇总到最高层次。 -
适用场景:需要逐级汇总的场景,例如按地区、时间、产品类别等维度进行分层统计。
-
语法:
SELECT col1, col2, ..., aggregate_function(col)
FROM table
GROUP BY ROLLUP(col1, col2, ...);
示例:假设有一个销售数据表 sales,包含 region(地区)、year(年份)、sales_amount(销售额)。使用 ROLLUP 按地区和年份汇总:
SELECT
region,
year,
SUM(sales_amount) AS total_sales
FROM sales
GROUP BY ROLLUP(region, year);
region year total_sales
East 2023 3000.00
East 2024 3400.00
East NULL 6400.00
West 2023 4000.00
West 2024 4600.00
West NULL 8600.00
NULL NULL 15000.00
结果:
- 每个地区每年的销售额。
- 每个地区的总销售额(
year为NULL)。 - 整体总销售额(
region和year均为NULL)。
b、CUBE
定义:CUBE 生成所有可能的汇总组合,适用于多维分析。
适用场景:需要全面分析所有维度组合的场景,例如同时按地区、年份、产品类别统计。
语法:
SELECT col1, col2, ..., aggregate_function(col)
FROM table
GROUP BY CUBE(col1, col2, ...);
示例:按地区、年份和产品类别统计销售额:
SELECT
region,
year,
product_category,
SUM(sales_amount) AS total_sales
FROM sales
GROUP BY CUBE(region, year, product_category);
结果:
- 每个地区、年份、产品类别的销售额。
- 每个地区、年份的汇总(
product_category为NULL)。 - 每个地区、产品类别的汇总(
year为NULL)。 - 每个年份、产品类别的汇总(
region为NULL)。 - 整体总销售额(所有列为
NULL)。
c、GROUPING SETS
定义:GROUPING SETS 允许用户指定一组列的组合进行独立聚合,提供灵活的定制化汇总。
适用场景:需要特定维度组合的场景,例如只统计某些维度的汇总,而忽略其他组合。
语法:
SELECT col1, col2, ..., aggregate_function(col)
FROM table
GROUP BY GROUPING SETS (
(col1),
(col2),
(col1, col2),
...
);
示例:只统计以下组合:
- 每个地区的总销售额。
- 每年的总销售额。
- 每个地区每年的销售额。
SELECT
region,
year,
SUM(sales_amount) AS total_sales
FROM sales
GROUP BY GROUPING SETS (
(region),
(year),
(region, year)
);
结果:
- 每个地区的总销售额(
year为NULL)。 - 每年的总销售额(
region为NULL)。 - 每个地区每年的销售额。
2)聚合汇总结果
a、GROUPING
定义:GROUPING 函数用于判断某一列是否在汇总行中被省略(即显示为 NULL)。如果是,则返回 1;否则返回 0。
适用场景:
- 区分汇总行和普通数据行。
- 在结果集中添加标识列,明确哪些维度被汇总。
语法:
GROUPING(column_name)
示例:假设我们有一个销售数据表 sales,使用 ROLLUP 按地区和年份汇总:
SELECT
region,
year,
SUM(sales_amount) AS total_sales,
GROUPING(region) AS is_region_grouped,
GROUPING(year) AS is_year_grouped
FROM sales
GROUP BY ROLLUP(region, year);
结果:
| region | year | total_sales | is_region_grouped | is_year_grouped |
|---|---|---|---|---|
| East | 2023 | 3000.00 | 0 | 0 |
| East | 2024 | 3400.00 | 0 | 0 |
| East | NULL | 6400.00 | 0 | 1 |
| West | 2023 | 4000.00 | 0 | 0 |
| West | 2024 | 4600.00 | 0 | 0 |
| West | NULL | 8600.00 | 0 | 1 |
| NULL | NULL | 15000.00 | 1 | 1 |
解释:
- 当
is_region_grouped = 1时,表示region列被汇总(显示为NULL)。 - 当
is_year_grouped = 1时,表示year列被汇总(显示为NULL)。
b、GROUPING_ID
定义:GROUPING_ID 函数返回一个整数,用于唯一标识当前行的汇总级别。它是基于 GROUPING 函数的结果计算的二进制编码。
适用场景:
- 快速识别汇总行的类型。
- 在结果集中添加一个汇总标识列,方便后续处理。
语法:
GROUPING_ID(column1, column2, ...)
示例:继续使用 ROLLUP 按地区和年份汇总:
SELECT
region,
year,
SUM(sales_amount) AS total_sales,
GROUPING_ID(region, year) AS grouping_level
FROM sales
GROUP BY ROLLUP(region, year);
结果:
| region | year | total_sales | grouping_level |
|---|---|---|---|
| East | 2023 | 3000.00 | 0 |
| East | 2024 | 3400.00 | 0 |
| East | NULL | 6400.00 | 1 |
| West | 2023 | 4000.00 | 0 |
| West | 2024 | 4600.00 | 0 |
| West | NULL | 8600.00 | 1 |
| NULL | NULL | 15000.00 | 3 |
解释:grouping_level 的值是 GROUPING函数结果的二进制编码的十进制表示。
(region, year)普通行:GROUPING(region)=0,GROUPING(year)=0→00(二进制)→0(十进制)。(region, NULL)年份汇总行:GROUPING(region)=0,GROUPING(year)=1→01(二进制)→1(十进制)。(NULL, NULL)地区和年份汇总行:GROUPING(region)=1,GROUPING(year)=1→11(二进制)→3(十进制)。
三、分析函数(窗口函数)
分析函数,也称为窗口函数,是一种在 SQL 查询中对数据集中的行进行复杂计算的函数。窗口函数的特点在于,它们不会减少查询结果的行数,而是为每一行增加一个新的计算结果。窗口函数适用于多种分析场景,如计算滚动合计、排名以及移动平均等。
1、基本概念介绍
1)处理顺序
使用分析函数的查询处理可以分为三个阶段。
- 执行所有的连接、
WHERE、GROUPBY和HAVING子句。- 执行所有的连接操作
- 应用
WHERE子句进行过滤 - 执行
GROUPBY分组 - 应用
HAVING子句对分组结果过滤
- 将结果集提供给分析函数,并进行所有必要的计算。
- 将基础查询的结果集提供给分析函数
- 根据
PARTITIONBY子句创建分区 - 在每个分区内应用窗口定义
- 执行所有必要的分析计算
- 如果查询的末尾包含
ORDERBY子句,则处理该子句以实现精确的输出排序。
2)术语解释
a、结果集分区
- 定义:通过
PARTITIONBY子句将结果集划分为多个行组(分区) - 特点:
- 与表分区功能无关,是查询处理中的逻辑分区
- 分析函数在每个分区内独立计算
- 示例:
PARTITION BY department将按部门划分数
b、窗口
- 定义:为分区内每行定义的滑动数据范围,用于计算
- 窗口类型:
- 累积窗口:起始行固定(如分区首行),结束行滑动。示例:累积和函数
- 滑动窗口:起始和结束行都滑动,示例:移动平均值
-
窗口大小:
- 可包含分区内所有行
- 也可定义为单行滑动窗口
-
边界处理:靠近分区边界时,窗口自动缩减
-
窗口定义语法:
ROWS BETWEEN [m] PRECEDING AND [n] FOLLOWING -- 或简写 ROWS [m] PRECEDING -- 等价于 BETWEEN m PRECEDING AND CURRENT ROW -
当前行处理:
- 当前行总是包含在计算中
- 计算
n个项目时,窗口应指定为( n - 1 )。示例:5天平均值应使用ROWS BETWEEN 4 PRECEDING AND CURRENT ROW
c、当前行
-
作用:作为窗口开始和结束的参考点
-
应用示例:
-
中心移动平均:包含当前行、前 6 行和后 6 行(共 13 行窗口)
-
示例语法:
-
AVG(value) OVER ( PARTITION BY category ORDER BY date ROWS BETWEEN 6 PRECEDING AND 6 FOLLOWING )
-


