订阅更新

SQL 展开多个数组为什么会翻倍?怎么保留位置对应关系?

面试问 242 约 4 分钟读完

本文目录
  1. 面试怎么回答
  2. 两次展开,不一定表示逐个配对
  3. 位置相关的数据,先装进同一个结构
  4. 长度不一致,不能假装对齐成功
  5. 一个典型场景:DISTINCT 没能救回订单金额
  6. 最好的修复可能在数据合同里
  7. 参考资料

面试怎么回答

我先判断多个数组是独立集合还是按位置对应。独立展开会产生组合;如果位置相关,就先用 arrays_zip 形成结构体数组,再用 posexplode 展开并保留下标。随后检查长度、NULL 和空数组,避免把占位行当有效明细。最后核对指标粒度,防止订单金额在展开后重复求和。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

一个订单里有两个商品、两个数量,展开以后却出现四行。SQL 没报错,结果看起来也都是合法值,但“哪个数量属于哪个商品”已经被改写了。这类问题的关键不是去重,而是先认清数组之间的关系。

先区分独立集合与按位置配对的数组;独立展开会形成组合,位置配对应先合并结构再展开,并验证长度和空值。

两次展开,不一定表示逐个配对

假设商品数组是 [A, B],数量数组是 [1, 2],业务含义是 A 买一件、B 买两件。在 Spark SQL 中连续使用两个独立的 LATERAL VIEW explode,会把每个商品再与每个数量组合,得到 A-1、A-2、B-1、B-2 四行。

如果两个数组分别表示可选颜色和可选尺码,组合也许正是业务要的结果。问题不在于两次 explode 永远错误,而在于它实现的是组合关系,不会自动理解“第一个配第一个”。写 SQL 前要把目标行的粒度说清楚。

位置相关的数据,先装进同一个结构

针对平行数组,可以先用 arrays_zip 按位置构造结构体数组,再展开一次。下面是 Spark SQL 的完整小例子,输入明确为两个非空且等长的数组:

WITH src AS (
  SELECT 1001 AS order_id,
         array('A', 'B') AS skus,
         array(1, 2) AS qtys
)
SELECT order_id, pos, item.skus AS sku, item.qtys AS qty
FROM src
LATERAL VIEW posexplode(arrays_zip(skus, qtys)) e AS pos, item;

结果是位置 0 对应 A-1,位置 1 对应 B-2。posexplode 保留位置,方便与原始报文核对;arrays_zip 负责位置配对。函数名不同、SQL 方言不同的时候,不要直接照搬别的引擎的列别名和语法。

位置配对与独立组合,是两种关系

长度不一致,不能假装对齐成功

Spark 的 arrays_zip 对较短数组缺少的位置补 NULL。如果商品有三个、数量只有两个,第三行出现空数量,这只是暴露了输入缺失,不是计算出了零件商品。不要马上 COALESCE 为 0,让接口错误悄悄变成正常销售记录。

应先区分三个问题:数组本身为空值、数组长度为零、数组内部某个元素为空值。是否保留没有子项的父记录,要按输出粒度选择普通展开还是 outer 展开;外部关联式展开产生的占位行也不能直接计作一个有效商品。

对于长度异常,可以把原始报文和异常原因放到待处理区,单独统计覆盖率。是否允许部分商品先入仓,需要业务接受明确的“不完整”状态,不能只为了行数对齐就截掉较长数组的尾部。

一个典型场景:DISTINCT 没能救回订单金额

比如开发发现明细变多后加 DISTINCT,行数却没有恢复。最初判断源端重复上报,检查订单号也确实重复;逐条查看后才发现四个商品数量组合互不相同,去重当然不会删除它们。

更隐蔽的问题是订单总金额跟着每个明细复制。即使配对正确,只要按展开行直接 SUM 订单总金额,订单有两件商品就会重复累加两次。数组对齐解决字段关系,指标粒度还要另外解决。

修正时先验证订单加明细标识的唯一性,再按业务计算行金额;订单级金额留在订单粒度,或者按明确分摊规则进入明细。不能用 MAX 随意掩盖一个本来混了多张订单的分组。

最好的修复可能在数据合同里

如果可以推动上游改造,优先让报文使用 [{sku:A,qty:1},{sku:B,qty:2}] 这类结构体数组,字段关系天然在同一个子对象中。比起维护多个平行数组,更容易做字段校验和版本演进。

位置也不一定是稳定的业务主键。上游重新排序或插入元素后,同一个下标可能代表不同商品行;需要更新、删除或追溯时,应尽量保留源端 line_id。验收同时检查展开前后父记录覆盖、有效子项数量、字段配对和金额守恒。

参考资料

刷题进度保存在当前浏览器