表连接,一表有,一表无的几种方式对比:left join 筛选 is NULL ,minus ,not In,not exists
我们来对比一下这几种方式在逻辑、性能、可读性 上的差异,并给出针对Oracle/MySQL/Hive的最佳实践。
一、核心结论速查表
| 方式 | 逻辑含义 | 返回结果 | 处理 NULL 的行为 | 性能特点 (Hive/大数据) | 推荐度 |
|---|---|---|---|---|---|
| LEFT JOIN + IS NULL | A 表所有行,关联不上 B 表的行 | 只返回 A 表字段,B 表字段为 NULL | NULL 就是 NULL |
✅ 高效,适合大表关联,能利用 MapJoin | ⭐ 强烈推荐 |
| NOT EXISTS | 检查 B 表中是否存在满足条件的行 | 返回 A 表完整行 | ✅ 正确处理,不受 NULL 影响 | 较高效,一般优于 NOT IN | ⭐ 推荐 |
| MINUS / EXCEPT | A 表减去 B 表的集合差 | 返回 A 表完整行 | ✅ 正确处理 (Oracle将NULL视为相同) | ❌ 在大数据量时性能差,消耗内存 | 不推荐 (大数据场景) |
| NOT IN | A 表中不在 B 表子查询结果里的行 | 返回 A 表完整行 | ❌ 有陷阱 。若子查询结果包含 NULL,整个查询结果为空 |
❌ 性能最差,不建议使用 | ❌ 避坑 (大数据场景) |
MapJoin 是 Hive 中的一种优化 Join 的方式 ,专门用来把小表加载到内存 中,然后分发到各个 Map 任务中,在 Map 阶段直接完成 Join,从而避免耗时的 Shuffle 和 Reduce 阶段。
简单来说就是:小表广播,Map 端直接关联。
对比项 普通 Reduce Join MapJoin 触发条件 大表 Join 大表 一张表很小(默认 < 25MB) 执行方式 按 Join 键 Shuffle,然后在 Reduce 阶段完成 Join 将小表完全加载到内存,Map 端读取大表时直接关联 性能 慢,有网络开销和排序开销 非常快,避免 Shuffle 使用方式 自动判断,也支持手动 /*+ MAPJOIN(small_table) */开启典型场景:事实表(几亿行)和维度表(几百行,如机构代码表、产品信息表)做关联查询时,MapJoin 是首选。
在银行数仓 ETL 中,这是每天都会用到的优化技巧。
在分布式计算(如 Hadoop/Spark)中,Shuffle 指的是数据跨节点重新分区的过程。
简单来说:把 Map 阶段输出的数据,按照 Key 重新分发到各个 Reduce 节点,确保相同的 Key 进入同一个 Reduce 任务。
📊 核心要点速览
对比项 Shuffle 前 (Map 端) Shuffle 中 Shuffle 后 (Reduce 端) 数据分布 数据分散在各个 Map 节点,按块存储 数据通过网络重新排列分发 相同 Key 的数据汇聚到同一个 Reduce 节点 主要操作 处理输入分片,输出中间结果 分区 (Partition) → 排序 (Sort) → 聚合 (Spill) → 传输 (Fetch) 对汇聚后的数据进行最终计算 典型代价 计算开销 网络传输 + 磁盘I/O + 序列化/反序列化 内存/CPU 开销
🚫 为什么 Shuffle 是"昂贵的"?
Shuffle 是分布式计算中性能消耗最大的环节,因为涉及:
写磁盘:Map 端会将中间结果写入本地磁盘(防止内存溢出)。
网络传输:数据需要跨节点通过网络传输,容易成为瓶颈。
排序与合并:在传输前需要对数据进行排序和合并,消耗 CPU。
内存压力:Reduce 端需要将拉取的数据保存在内存中进行聚合。
📌 在 Hive/Spark 中的应用
触发 Shuffle 的操作:
GROUP BY、DISTINCT、JOIN、ORDER BY、UNION ALL等。减少 Shuffle 的优化技巧:
MapJoin:小表 Join 时不触发 Shuffle(前面提到的用法)。
Bucket Join:按相同分桶字段关联时,数据可能已在正确分区,可减少 Shuffle。
分区剪枝:只扫描必要的分区,减少输入数据量。
💎 一句话总结
Shuffle 就是大数据场景下"重分发数据"的代价。它在功能上必不可少(让数据按 Key 汇聚),但在性能上开销巨大。理解了 Shuffle,你就理解了为什么 MapJoin 那么快------因为它完全绕开了 Shuffle。
二、详细解释与代码示例
我们用两张简单的表来演示:
sql
-- 员工表
SELECT * FROM A;
ID | NAME
----|------
1 | Alice
2 | Bob
3 | Charlie
4 | NULL (NULL 值)
5 | David
-- 部门表 (包含部分员工)
SELECT * FROM B;
ID | DEPT
----|------
1 | HR
2 | IT
3 | NULL (NULL 值部门)
1. LEFT JOIN + IS NULL (标准大数据写法)
sql
SELECT A.*
FROM A
LEFT JOIN B ON A.ID = B.ID
WHERE B.ID IS NULL;
-
执行过程 :先做关联,然后过滤掉能关联上
B.ID的行(即非 NULL)。 -
结果 :返回
ID为4和5的行。ID=3的行因为能关联上B.ID=3(尽管DEPT是NULL),所以被过滤掉了。 -
优点:这是 Hive/Spark SQL 中最推荐的写法,大数据量下性能好,能充分利用 MapJoin。
2. NOT EXISTS (标准 SQL,推荐)
sql
SELECT A.*
FROM A
WHERE NOT EXISTS (
SELECT 1 FROM B WHERE A.ID = B.ID
);
-
执行过程:针对 A 表的每一行,去 B 表检查是否存在匹配的 ID。
-
结果 :同样返回
ID为4和5的行,正确排除了ID=3。 -
优点 :这是标准的 SQL 写法,跨平台通用性好,逻辑清晰,且不受子查询中
NULL值影响。
3. MINUS (Oracle) / EXCEPT (其他数据库)
sql
-- Oracle
SELECT ID FROM A
MINUS
SELECT ID FROM B;
-
执行过程:对两个结果集做集合差运算,自动去重。
-
结果 :返回
4和5。 -
缺点 :在 Hive/Spark 中,你需要使用
EXCEPT关键字,但它在处理大数据量时通常需要shuffle操作,性能不如LEFT JOIN,且会进行隐式的DISTINCT操作,如果不是需要唯一值,可能会有额外开销。
4. NOT IN (使用时要万分小心) ⚠️
sql
SELECT *
FROM A
WHERE A.ID NOT IN (SELECT B.ID FROM B);
-
执行过程:先执行子查询,然后判断 A.ID 是否在结果集中。
-
结果 :如果子查询
SELECT B.ID FROM B的结果中包含NULL,那么整个查询结果会返回空!-
在这个例子中,
B表的ID列是(1, 2, 3),没有NULL,所以也能正常返回4和5。 -
但如果
B表有一个ID IS NULL,则NOT IN逻辑会变成:(A.ID NOT IN (1,2,3,NULL))。由于NULL代表未知,和任何已知值的比较结果也是未知,因此整个条件的结果是FALSE或UNKNOWN,导致查不出任何结果。
-
-
结论 :除非你100%确定子查询结果集中不会包含
NULL,否则强烈避免使用NOT IN。在大数据开发中,数据质量往往不可控,因此这是一个需要小心使用的操作。
三、性能对比与最佳实践 (重点)
| 对比维度 | LEFT JOIN + IS NULL | NOT EXISTS | NOT IN | MINUS/EXCEPT |
|---|---|---|---|---|
| Hive/Spark SQL | ✅ 首选。能利用 MapJoin 进行优化。 | ✅ 次选。优化器通常能处理得很好。 | ❌ 性能差,且有 NULL 陷阱。 | ❌ 性能差,触发 Shuffle。 |
| Oracle/MySQL | 性能较好,如果 B 表的关联键有索引,效果更好。 | ✅ 首选。通常比 NOT IN 更优。 | 如果子查询结果无 NULL,且优化器能转为 NOT EXISTS,则还行。 | ✅ 如果 A 和 B 是结果集较小的表,MINUS 语义清晰,性能不错。 |
| 可读性 | 中等。需要理解 LEFT JOIN + 过滤 NULL 的组合逻辑。 |
高。语义最直接:"不存在于"。 | 低。有隐藏的 NULL 陷阱,需要额外注意。 | 高。集合差运算,语义清晰。 |
四、给你最实用的建议
根据你未来的工作场景(混合使用 Oracle/MySQL 取数和 Hive ETL),这里给你三条可以直接用的原则:
-
在 Hive/Spark SQL 中写 ETL 时:
- 默认使用
LEFT JOIN + IS NULL。这是大数据平台的"标准写法",性能最好,也符合一般的数据处理习惯。
sql-- 推荐写法 (Hive) SELECT /*+ MAPJOIN(b) */ a.* FROM dwd_table_a a LEFT JOIN dim_table_b b ON a.key = b.key WHERE b.key IS NULL; - 默认使用
-
在写跨平台的通用脚本时:
- 使用
NOT EXISTS。它是标准的 SQL,所有数据库都支持,逻辑清晰,且没有NULL的陷阱,是最稳妥的选择。
- 使用
-
绝对要避免的事情:
- 在处理大数据的场景下,不要用
NOT IN。你无法控制 B 表关联键的数据质量,一行NULL就能让你的整个任务逻辑出错,且排查起来非常痛苦。
- 在处理大数据的场景下,不要用
总结一下:
当你要找"A 表有,B 表无"的数据时,在 Oracle/MySQL 中,NOT EXISTS 是稳妥的选择;
在 Hive/Spark SQL 中,LEFT JOIN + IS NULL 是性能最好的选择。
NOT IN 可能有坑,慎用;
MINUS/EXCEPT 更适用于结果集较小的集合运算。
LEFT JOIN ... IS NULL 在 Hive/Spark SQL 中是比较主流的写法,如果你想了解它配合 MapJoin 的优化技巧,我可以再展开讲讲。
A join B on A.xx=B.xx 和 A join B on B.xx=A.xx 有区别吗
在 SQL 中,A JOIN B ON A.xx = B.xx 与 A JOIN B ON B.xx = A.xx 完全没有区别。
因为 = 运算符是对称的,A.xx = B.xx 和 B.xx = A.xx 表达的是同一个等值条件。
数据库查询优化器会将它们视为相同的连接条件,生成相同的执行计划,最终结果也完全一致。
总结:
无论把哪个表的字段写在等号左边,只要比较的字段和值相同,语义和结果都相同。
你可以根据个人习惯或可读性选择任意一种写法。
在SQL优化中,提到的表连接,某些情况下连接条件的优化写法有哪些
已思考(用时 9 秒)
我们被问到:"在SQL优化中,提到的表连接,某些情况下连接条件的优化写法有哪些"。需要给出一些常见的优化写法建议。可能包括:将过滤条件提前、使用合适的连接类型、避免函数在连接条件上、确保数据类型匹配、使用复合索引覆盖连接条件等。需要具体一些。
用户要求:在SQL优化中,表连接的连接条件的优化写法。回答要专业且实用。
我将列出几点:
-
将连接条件中的字段类型保持一致,避免隐式类型转换,否则索引失效。
-
避免在连接条件中使用函数或表达式,如
ON DATE(a.date_col) = b.date_col会导致索引失效,应改为a.date_col = b.date_col或者调整函数到等号另一边。 -
对于多表连接,尽量将过滤条件(WHERE)与连接条件(ON)分开,但优化器通常自动处理,但可以注意将能过滤大量数据的条件提前。
-
使用小结果集驱动大结果集:在嵌套循环连接中,有意识地让返回行数少的表作为驱动表(但现代优化器会判断,也可以使用提示)。
-
使用合适的连接类型:内连接、左外连接等,确保连接条件正确,避免产生笛卡尔积。
-
对于复合连接条件,注意索引的顺序:连接条件的字段顺序应与复合索引的列顺序匹配。
-
使用 EXISTS 替代 IN 等某些情况下的连接?但那是子查询优化。
-
避免使用 OR 连接条件,可以考虑 UNION ALL 分离。
-
对于不等值连接(范围连接),可能需要其他优化手段,但一般连接条件是等值。
注意:用户问的是"连接条件的优化写法",不是通用的SQL优化。所以聚焦于连接条件的写法。
在 SQL 优化中,连接条件的写法虽然看似简单,但一些细节会显著影响执行效率。
以下是常见的优化写法建议:
1. 确保连接字段数据类型一致
-
问题 :连接字段类型不同(如
INT与VARCHAR)会触发隐式类型转换,导致索引失效。 -
优化:统一字段类型。例如:
sql-- 差:a.id(INT)与 b.id(VARCHAR)比较 ON a.id = b.id -- b.id 会被隐式转换,不走索引 -- 优:显式转换或统一类型 ON a.id = CAST(b.id AS INT)最佳做法是设计时保持关联字段类型一致。
2. 避免在连接条件中使用函数或表达式
-
问题 :对字段使用函数(如
DATE()、UPPER())或算术运算,会使索引失效。 -
优化:将函数移至等号另一侧的常量上,或使用计算列/冗余字段。
sql-- 差 ON DATE(a.create_time) = b.date_col -- 优 ON a.create_time >= b.date_col AND a.create_time < b.date_col + INTERVAL 1 DAY
3. 将能过滤大量数据的条件提前放入 ON 或 WHERE
-
原则 :对于
INNER JOIN,过滤条件放在ON与WHERE效果相同,但习惯上建议将连接条件与过滤条件分开,并确保过滤条件能尽早减少参与连接的数据量。sql-- 优:先过滤订单表,再连接 SELECT * FROM orders o JOIN order_items oi ON o.id = oi.order_id AND o.status = '已完成'对于
LEFT JOIN,右表的过滤条件必须放在ON中才能保留左表全量数据,但若想过滤右表后再连接,应使用子查询。
4. 使用等值连接优先,非等值连接谨慎处理
-
等值连接 (
=)能充分利用哈希连接或索引嵌套循环连接。 -
非等值连接 (
<、>、<>)效率较低,可尝试改写为BETWEEN或使用窗口函数/子查询提前缩小范围。sql-- 差 ON a.salary > b.min_salary AND a.salary < b.max_salary -- 可优化为(若 b 表有明确区间) ON a.salary BETWEEN b.min_salary AND b.max_salary
5. 复合连接条件的字段顺序应与索引一致
-
问题 :复合索引
(col1, col2),但连接条件顺序为ON a.col2 = b.col2 AND a.col1 = b.col1,优化器通常仍可使用,但建议按索引列顺序写,提升可读性并避免某些老旧优化器的误判。sql-- 假设 idx_composite (product_id, region_id) -- 建议: ON a.product_id = b.product_id AND a.region_id = b.region_id
6. 避免使用 OR 串联多个连接条件
-
问题 :
ON a.id = b.id OR a.name = b.name难以使用索引,通常会导致全表扫描。 -
优化 :拆分为
UNION ALL:sqlSELECT * FROM a JOIN b ON a.id = b.id UNION ALL SELECT * FROM a JOIN b ON a.name = b.name WHERE a.id != b.id -- 去重(视业务需求)
7. 小表驱动大表时的写法(针对嵌套循环连接)
-
虽然多数优化器会自动选择驱动表,但若使用
STRAIGHT_JOIN或ORDER BY强制驱动顺序,可将小表写在前面,大表写在后面,并且连接条件的索引要建立在大表的连接字段上。sql-- 小表 small,大表 big SELECT * FROM small s STRAIGHT_JOIN big b ON b.key = s.key -- big.key 必须有索引
8. 使用 EXISTS 替代 JOIN 去重时
-
若只需要检查存在性,不需要返回右表字段,用
EXISTS往往比JOIN更高效,且能提前终止扫描:sql-- 差:可能产生重复行 SELECT DISTINCT a.* FROM a JOIN b ON a.id = b.id -- 优 SELECT * FROM a WHERE EXISTS (SELECT 1 FROM b WHERE b.id = a.id)
总结
| 优化要点 | 推荐写法 |
|---|---|
| 类型一致 | ON a.int_col = b.int_col |
| 无函数 | 避免 ON DATE(a.dt) = b.dt |
| 提前过滤 | 在 ON 中放置右表过滤条件(LEFT JOIN) |
| 等值优先 | 尽量使用 = |
| 索引顺序 | 复合索引与连接条件顺序对齐 |
避免 OR |
拆成 UNION ALL |
| 小表驱动 | 确保大表连接字段有索引 |
| 存在性检查 | 用 EXISTS 代替 JOIN |
遵循这些写法,能让查询优化器更有效地选择索引和连接算法,显著提升复杂查询的性能
数据去重
数据去重(Deduplication)是 ETL 过程中最频繁的操作之一。
我们来全面对比一下 Hive/Spark SQL 中常见的去重方式。
一、核心结论速查表
| 方式 | 核心语法 | 适用场景 | 保留哪一行 | 性能 (大数据量) | 推荐度 |
|---|---|---|---|---|---|
| ROW_NUMBER() | ROW_NUMBER() OVER(PARTITION BY col ORDER BY col) |
按业务规则去重(如保留最新一笔) | 可灵活控制(排序第一行) | ⭐⭐⭐ 中等(需要排序) | ⭐ 强烈推荐 |
| GROUP BY | GROUP BY col1, col2 |
只需去重后的唯一键,不需要其他字段 | 不保留原始行,需用聚合函数 | ⭐⭐⭐⭐ 较好(Shuffle 开销小) | 推荐 |
| DISTINCT | SELECT DISTINCT * |
所有字段完全相同的简单去重 | 任意一行(无区别) | ⭐⭐ 一般(类似 GROUP BY) | 简单场景可用 |
| DISTINCT + 部分字段 | SELECT DISTINCT col1, col2 |
只返回去重键,不返回其他字段 | 只返回指定列 | ⭐⭐⭐ 较好 | 部分场景适用 |
| FIRST_VALUE/LAST_VALUE | FIRST_VALUE(col) OVER(...) |
需要保留多个字段,且按规则取首/尾值 | 可灵活控制 | ⭐⭐⭐ 中等 | 复杂取数场景 |
| collect_set + explode | 先聚合再展开 | 需要先聚合再还原的场景 | 不保留原始行 | ⭐⭐ 较差(两次 Shuffle) | 不推荐 |
二、详细解释与代码示例
假设我们有如下订单表 orders:
| order_id | cust_id | order_date | amount |
|---|---|---|---|
| 1 | A | 2026-06-01 | 100 |
| 2 | A | 2026-06-02 | 200 |
| 3 | B | 2026-06-01 | 150 |
| 4 | B | 2026-06-03 | 250 |
| 5 | C | 2026-06-02 | 300 |
需求:每个客户只保留最新的一笔订单(按 order_date 排序)。
1. ROW_NUMBER() (最常用,推荐)
sql
SELECT order_id, cust_id, order_date, amount
FROM (
SELECT *,
ROW_NUMBER() OVER(PARTITION BY cust_id ORDER BY order_date DESC) AS rn
FROM orders
) t
WHERE rn = 1;
结果:每个客户保留 order_date 最大的一行(最新订单)。
-
客户 A 保留 order_id=2
-
客户 B 保留 order_id=4
-
客户 C 保留 order_id=5
优点:
-
✅ 可灵活控制保留哪一行(通过 ORDER BY 和 WHERE rn = N)
-
✅ 可同时返回所有字段
-
✅ 逻辑清晰,易于理解和维护
-
✅ 支持复杂去重规则(如按多字段排序)
缺点:
- ❌ 需要开窗排序,大数据量下有一定性能开销
适用场景:
-
按业务规则去重(保留最新、最旧、金额最大等)
-
需要保留完整行信息
-
这是 ETL 中最常用的方式
2. GROUP BY
sql
SELECT cust_id, MAX(order_date) AS latest_date, SUM(amount) AS total_amount
FROM orders
GROUP BY cust_id;
结果:每个客户的汇总信息,不是原始行。
优点:
-
✅ 性能较好,Shuffle 开销相对较小
-
✅ 适合需要聚合统计的场景
缺点:
-
❌ 只能返回分组字段和聚合结果
-
❌ 无法保留原始行的其他字段(如 order_id)
适用场景:
-
只需要去重后的唯一键 + 聚合统计
-
不需要返回原始行的其他字段
3. DISTINCT (所有字段完全相同)
sql
SELECT DISTINCT * FROM orders;
结果:返回所有字段组合唯一的行。如果所有字段完全相同才去重。
优点:
-
✅ 语法简单,易读
-
✅ 适合发现重复数据
缺点:
-
❌ 要求所有字段都相同才算重复
-
❌ 无法按业务规则去重(如"同一个客户只保留最新订单")
-
❌ 结果集中只有去重后的行,无法控制保留哪一行
适用场景:
-
简单的全字段去重
-
数据质量检查
4. DISTINCT + 部分字段
sql
SELECT DISTINCT cust_id FROM orders;
结果:A, B, C
优点:
-
✅ 语法简单,性能较好
-
✅ 适合只获取唯一键
缺点:
- ❌ 只能返回去重的字段,无法返回其他字段
适用场景:
- 只需要获取唯一的键值列表
5. FIRST_VALUE / LAST_VALUE (开窗取首尾值)
sql
SELECT DISTINCT
cust_id,
FIRST_VALUE(order_id) OVER(PARTITION BY cust_id ORDER BY order_date DESC) AS latest_order_id,
FIRST_VALUE(amount) OVER(PARTITION BY cust_id ORDER BY order_date DESC) AS latest_amount
FROM orders;
结果:每个客户最新订单的 order_id 和 amount。
优点:
-
✅ 可以灵活取首行或末行的某个字段
-
✅ 不需要嵌套子查询
缺点:
-
❌ 语法比 ROW_NUMBER 复杂
-
❌ 每个字段都要写一个窗口函数
-
❌ 需要配合 DISTINCT 去重,可能会产生重复行
适用场景:
-
需要按规则取多个字段的"首/尾值"
-
不想使用子查询时
6. collect_set + explode (不推荐)
sql
SELECT explode(collect_set(col)) FROM table;
这是一种"绕远路"的做法,不推荐在大数据场景使用。
三、性能与选型建议
场景一:按业务规则去重,保留完整行
sql
-- ✅ 推荐:ROW_NUMBER()
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER(PARTITION BY key ORDER BY sort_col DESC) AS rn
FROM source
) t WHERE rn = 1;
场景二:只需要唯一键,不需要其他字段
sql
-- ✅ 推荐:DISTINCT 或 GROUP BY
SELECT DISTINCT cust_id FROM orders;
-- 或
SELECT cust_id FROM orders GROUP BY cust_id;
场景三:需要按唯一键聚合统计
sql
-- ✅ 推荐:GROUP BY
SELECT cust_id, SUM(amount), COUNT(1) FROM orders GROUP BY cust_id;
场景四:所有字段完全相同才去重
sql
-- ✅ 推荐:DISTINCT
SELECT DISTINCT * FROM table;
场景五:Hive/Spark 大表去重优化
sql
-- 技巧:先用 GROUP BY 缩小数据量,再用 ROW_NUMBER
WITH dedup_key AS (
SELECT key, MAX(version) AS max_version
FROM source
GROUP BY key
)
SELECT t.*
FROM source t
JOIN dedup_key d ON t.key = d.key AND t.version = d.max_version;
四、ROW_NUMBER() vs GROUP BY vs DISTINCT 对比
| 对比维度 | ROW_NUMBER() | GROUP BY | DISTINCT |
|---|---|---|---|
| 灵活性 | ⭐⭐⭐⭐⭐ 最高 | ⭐⭐ 较低 | ⭐ 最低 |
| 保留完整行 | ✅ 可以 | ❌ 不可以 | ✅ 可以(全字段相同) |
| 控制保留哪一行 | ✅ 可以 | ❌ 不可以 | ❌ 不可以 |
| 性能(大表) | ⭐⭐⭐ 中等 | ⭐⭐⭐⭐ 较好 | ⭐⭐⭐ 中等 |
| Shuffle 次数 | 1 次(排序) | 1 次 | 1 次 |
| 可读性 | ⭐⭐⭐⭐ 高 | ⭐⭐⭐⭐ 高 | ⭐⭐⭐⭐⭐ 最高 |
| 是否支持复杂规则 | ✅ 支持 | ❌ 不支持 | ❌ 不支持 |
五、总结
| 你的需求 | 推荐方式 | 示例 |
|---|---|---|
| 按业务规则去重(如保留最新订单) | ROW_NUMBER() | ROW_NUMBER() OVER(PARTITION BY cust_id ORDER BY order_date DESC) |
| 只需要唯一键列表 | DISTINCT 或 GROUP BY | SELECT DISTINCT cust_id FROM orders |
| 需要按唯一键聚合统计 | GROUP BY | SELECT cust_id, SUM(amount) FROM orders GROUP BY cust_id |
| 全字段相同的简单去重 | DISTINCT | SELECT DISTINCT * FROM orders |
| 需要取首行/末行的特定字段 | FIRST_VALUE/LAST_VALUE | FIRST_VALUE(amount) OVER(...) |
六、一句话总结
-
能按业务规则去重且保留完整行 → 用
ROW_NUMBER() -
只需要唯一键或聚合统计 → 用
GROUP BY -
全字段相同的简单去重 → 用
DISTINCT
ROW_NUMBER() 是大数据 ETL 中去重的"瑞士军刀",能应对绝大多数场景。
如果你需要我针对某个具体场景(比如保留最新状态、去重后取多个字段等)再展开讲讲,随时告诉我。