DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
雨云;给项目一台云服务器
RackNerd;$21.99/年 · 1GB KVM

MongoDB 聚合 $size 遇到缺失字段时报 must be an array

用 $isArray 和 $cond 区分数组与缺失、null、错误类型,保留异常标记;避免仅用 ifNull 或批量改文档掩盖数据问题。

聚合管道用 $size 统计数组长度,部分记录却让整条查询报错:

The argument to $size must be an array, but was of type: missing

同一字段在某些文档里是数组,在另一些文档里缺失、为 null 或保存了字符串,都会影响这条表达式。$size 只接受数组;空数组是合法输入,缺失字段并不是空数组。

下面针对 MongoDB 聚合表达式,示例用于支持 $isArray 的当前版本。它不是查询条件里用于匹配固定数组长度的 $size 写法。

先确认送进表达式的值

例如,products 中的 result.template 应当是模板名称数组。先保留实际查询的租户、权限和业务范围条件,再查看少量文档的该字段,区分以下形态:

{ result: { template: ["a", "b"] } }
{ result: { template: [] } }
{ result: { template: null } }
{ result: {} }
{ result: { template: "a,b" } }

前两种可以直接计算长度,后三种不能。还要检查前面的管道阶段是否已经投影掉字段,或把它改成其他类型。原始文档存在数组,不代表经过前面几步后仍然存在。

用数组判断保护长度计算

如果接口规定“非数组记为零,但同时保留类型异常标记”,可使用下面的只读聚合。集合名与路径均为示例:

db.products.aggregate([
  { $limit: 20 },
  {
    $project: {
      templateIsArray: { $isArray: "$result.template" },
      templateCount: {
        $cond: {
          if: { $isArray: "$result.template" },
          then: { $size: "$result.template" },
          else: 0
        }
      }
    }
  }
])

这里先由 $cond 选择分支,只有数组才进入 $size。空数组返回零且 templateIsArray 为 true;缺失、null、字符串等非数组返回零,但标记为 false,避免把坏数据悄悄等同于正常空数组。

$limit 只限制这次示例的检查数量,不代表样本能证明整个集合都符合模型。正式查询应保留业务范围,并按实际排序、分页要求安排阶段,不能照搬这个限制来计算全量排名。

业务需要区分“没有值”和“长度为零”时,可把 else: 0 改成 else: null,同步调整下游的排序和展示约定。不要为了得到一个数字而自动更新所有文档,把原来的字符串或对象改成空数组。

仅用 ifNull 不能处理所有错误类型

下面的表达式能把 null 或缺失字段替换成空数组:

{ $size: { $ifNull: ["$result.template", []] } }

但非空字符串、对象等值不是 null,仍会原样传入 $size 并报错。只有已经保证字段取值范围为数组、null 或缺失时,才适合采用这个简化版本。

也不要把“字段存在”当作“字段是数组”。一个存在的字符串同样会失败。若决定直接排除非数组文档,结果集合会变小,这与保留文档并返回零是不同的业务行为,需要明确选择。

按数据形态核对结果

在隔离样本里分别检查两元素数组、空数组、null、缺失和字符串。预期前两项的长度为 2、0,标记都为 true;后三项走约定的兜底分支且标记为 false。再核对前置投影、排序和分页没有改变需要保留的文档范围。

这项修复只改变查询表达式。保存原管道,输出约定不合适时恢复查询即可,不涉及文档数据回滚。若实际还需要清洗错误类型,应另行保留原值、制定转换规则并备份,不能把查询不再报错当作数据已经修复。