表连接(一表有,一表无)、表连接优化、数据去重

表连接,一表有,一表无的几种方式对比: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 是分布式计算中性能消耗最大的环节,因为涉及:

  1. 写磁盘:Map 端会将中间结果写入本地磁盘(防止内存溢出)。

  2. 网络传输:数据需要跨节点通过网络传输,容易成为瓶颈。

  3. 排序与合并:在传输前需要对数据进行排序和合并,消耗 CPU。

  4. 内存压力:Reduce 端需要将拉取的数据保存在内存中进行聚合。

📌 在 Hive/Spark 中的应用

  • 触发 Shuffle 的操作

    • GROUP BYDISTINCTJOINORDER BYUNION 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)。

  • 结果 :返回 ID45 的行。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。

  • 结果 :同样返回 ID45 的行,正确排除了 ID=3

  • 优点 :这是标准的 SQL 写法,跨平台通用性好,逻辑清晰,且不受子查询中 NULL 值影响。

3. MINUS (Oracle) / EXCEPT (其他数据库)

sql 复制代码
-- Oracle
SELECT ID FROM A
MINUS
SELECT ID FROM B;
  • 执行过程:对两个结果集做集合差运算,自动去重。

  • 结果 :返回 45

  • 缺点 :在 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,所以也能正常返回 45

    • 但如果 B 表有一个 ID IS NULL,则 NOT IN 逻辑会变成:(A.ID NOT IN (1,2,3,NULL))。由于 NULL 代表未知,和任何已知值的比较结果也是未知,因此整个条件的结果是 FALSEUNKNOWN,导致查不出任何结果。

  • 结论 :除非你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),这里给你三条可以直接用的原则:

  1. 在 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;
  2. 在写跨平台的通用脚本时:

    • 使用 NOT EXISTS 。它是标准的 SQL,所有数据库都支持,逻辑清晰,且没有 NULL 的陷阱,是最稳妥的选择。
  3. 绝对要避免的事情:

    • 在处理大数据的场景下,不要用 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.xxA JOIN B ON B.xx = A.xx 完全没有区别


因为 = 运算符是对称的,A.xx = B.xxB.xx = A.xx 表达的是同一个等值条件。

数据库查询优化器会将它们视为相同的连接条件,生成相同的执行计划,最终结果也完全一致。

总结

无论把哪个表的字段写在等号左边,只要比较的字段和值相同,语义和结果都相同。

你可以根据个人习惯或可读性选择任意一种写法。


在SQL优化中,提到的表连接,某些情况下连接条件的优化写法有哪些


已思考(用时 9 秒)

我们被问到:"在SQL优化中,提到的表连接,某些情况下连接条件的优化写法有哪些"。需要给出一些常见的优化写法建议。可能包括:将过滤条件提前、使用合适的连接类型、避免函数在连接条件上、确保数据类型匹配、使用复合索引覆盖连接条件等。需要具体一些。


用户要求:在SQL优化中,表连接的连接条件的优化写法。回答要专业且实用。

我将列出几点:

  1. 将连接条件中的字段类型保持一致,避免隐式类型转换,否则索引失效。

  2. 避免在连接条件中使用函数或表达式,如 ON DATE(a.date_col) = b.date_col 会导致索引失效,应改为 a.date_col = b.date_col 或者调整函数到等号另一边。

  3. 对于多表连接,尽量将过滤条件(WHERE)与连接条件(ON)分开,但优化器通常自动处理,但可以注意将能过滤大量数据的条件提前。

  4. 使用小结果集驱动大结果集:在嵌套循环连接中,有意识地让返回行数少的表作为驱动表(但现代优化器会判断,也可以使用提示)。

  5. 使用合适的连接类型:内连接、左外连接等,确保连接条件正确,避免产生笛卡尔积。

  6. 对于复合连接条件,注意索引的顺序:连接条件的字段顺序应与复合索引的列顺序匹配。

  7. 使用 EXISTS 替代 IN 等某些情况下的连接?但那是子查询优化。

  8. 避免使用 OR 连接条件,可以考虑 UNION ALL 分离。

  9. 对于不等值连接(范围连接),可能需要其他优化手段,但一般连接条件是等值。

注意:用户问的是"连接条件的优化写法",不是通用的SQL优化。所以聚焦于连接条件的写法。


在 SQL 优化中,连接条件的写法虽然看似简单,但一些细节会显著影响执行效率。


以下是常见的优化写法建议:


1. 确保连接字段数据类型一致

  • 问题 :连接字段类型不同(如 INTVARCHAR)会触发隐式类型转换,导致索引失效。

  • 优化:统一字段类型。例如:

    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,过滤条件放在 ONWHERE 效果相同,但习惯上建议将连接条件与过滤条件分开,并确保过滤条件能尽早减少参与连接的数据量。

    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

    sql 复制代码
    SELECT * 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_JOINORDER 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)
只需要唯一键列表 DISTINCTGROUP 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 中去重的"瑞士军刀",能应对绝大多数场景。


如果你需要我针对某个具体场景(比如保留最新状态、去重后取多个字段等)再展开讲讲,随时告诉我。

相关推荐
Irene19911 个月前
(课堂笔记)SQL 高级查询技巧:行列转换、重复数据、递归查询、连续登录
递归·去重·连续登录
Kinghiee2 个月前
从零打造生产级前端错误监控 SDK:架构设计与 Vue3 实践
前端·javascript·vue.js·去重·错误捕获·上报·离线持久化
嫂子的姐夫6 个月前
py连接Redis
数据库·redis·爬虫·去重
北漂编程小王子2 年前
java List对象集合中 如何根据集合中对象某几个属性组合去重
java·开发语言·list·去重·list对象集合去重
锦天3 年前
效果类似于 distinct 的列值去重/统计 Excel中有unique函数 numbers表格中无
excel·distinct·numbers·去重·unique
阿牛203 年前
小测试:HashSet可以插入重复的元素吗?
set·hash·去重