【问题标题】:Move elements within MongoDB document在 MongoDB 文档中移动元素
【发布时间】:2020-11-16 16:18:59
【问题描述】:

背景:

客户是具有名称字段的对象。

行是具有以下字段的对象:

  • inLine - 一系列客户
  • currentCustomer - 一位客户
  • processed - 一系列客户

集合 'line' 包含作为 line 对象的文档。


问题:

我正在尝试实现一个可以执行以下操作的程序:

  1. currentCustomer 推送到processed
  2. currentCustomer 设置为inLine 中的第一个元素
  3. 弹出inLine的第一个元素

由于一个字段的新值依赖于另一个字段的先前值,因此原子性在这里很重要。

到目前为止我尝试了什么:

天真的方法

db.collection('line').findOneAndUpdate({
    _id: new ObjectId(lineId),
}, {
    $set: {
        currentCustomer: '$inLine.0',
    },
    $pop: {
        inLine: -1,
    },
    $push: {
        processed: '$currentCustomer',
    },
});

但是,currentCustomer 设置为字符串,字面意思是“$inLine.0”,而processed 的字符串字面意思是“$currentCustomer”。

聚合方法

db.collection('line').findOneAndUpdate({
    _id: new ObjectId(lineId),
}, [{
    $set: {
        currentCustomer: '$inLine.0',
    },
    $pop: {
        inLine: -1,
    },
    $push: {
        processed: '$currentCustomer',
    },
}]);

但是,我收到以下错误:

MongoError:管道阶段规范对象必须只包含一个字段。

多阶段聚合方法

db.collection('line').findOneAndUpdate({
    _id: new ObjectId(lineId),
}, [{
    $set: {
        currentCustomer: '$inLine.0',
    },
}, {
    $pop: {
        inLine: -1,
    },
}, {
    $push: {
        processed: '$currentCustomer',
    },
}]);

但是,$pop$push 是无法识别的管道阶段名称。

我尝试只使用 $set 阶段来制作它,但结果非常丑陋,我仍然无法让它工作。

【问题讨论】:

标签: javascript mongodb mongodb-nodejs-driver


【解决方案1】:

基于turivishal's answer,这样解决了:

db.collection('line').findOneAndUpdate({
    _id: new ObjectId(lineId),
}, [{
    $set: {
        // currentCustomer = inLine.length === 0 ? null : inLine[0]
        currentCustomer: {
            $cond: [
                { $eq: [{ $size: '$inLine' }, 0] },
                null,
                { $first: '$inLine' },
            ],
        },
        // inLine = inLine.slice(1)
        inLine: {
            $cond: [
                { $eq: [{ $size: '$inLine' }, 0] },
                [],
                { $slice: ['$inLine', 1, { $size: '$inLine' }] },
            ],
        },
        // if currentCustomer !== null then processed.push(currentCustomer)
        processed: {
            $cond: [
                {
                    $eq: ['$currentCustomer', null],
                },
                '$processed',
                {
                    $concatArrays: [
                        '$processed', ['$currentCustomer'],
                    ],
                }
            ],
        },
    },
}]);

【讨论】:

  • 值得注意的是:这目前在 MongoDB M0 Sandbox(这是免费的)上不起作用,因为它运行的是 4.2 版,但 $first is new in 4.4
【解决方案2】:

我认为使用$push or $pop 进行简单更新是不可能的。

根据您的实验,聚合不支持根级别的直接$push, $pop 阶段,因此我已更正您的查询,

  • currentCustomer 检查条件,如果 inLine 的大小为 0,则返回 null 否则使用 $arrayElemAtinLine 数组中获取第一个元素,
  • inLine 检查条件如果 inLine 的大小为 0 然后返回 [] 否则使用 $slice$sizeinLine 数组中删除第一个元素
  • processed 使用$concatArrays$ifNull 连接两个数组来检查字段是否为空然后返回空数组,如果currentCustomer 为空则检查条件然后返回[] 否则返回currentCustomer
db.collection('line').findOneAndUpdate(
  { _id: new ObjectId(lineId), }, 
  [{
    $set: {
      currentCustomer: {
        $cond: [
          { $eq: [{ $size: "$inLine" }, 0] },
          null,
          { $arrayElemAt: ["$inLine", 0] }
        ]
      },
      inLine: {
        $cond: [
          { $eq: [{ $size: "$inLine" }, 0] },
          [],
          { $slice: ["$inLine", 1, { $size: "$inLine" }] }
        ]
      },
      processed: {
        $concatArrays: [
          { $ifNull: ["$processed", []] },
          {
            $cond: [
              { $eq: ["$currentCustomer", null] },
              [],
              ["$currentCustomer"]
            ]
          }
        ]
      }
    }
  }]
);

Playground

【讨论】:

  • 如果currentCustomer 为null,这会将null 推入processed
  • 如果inLine 为空,则错误为“$slice 的第三个参数必须为正数:0”
  • 如果inLine 为空,则应保持为空。
  • [] 是空数组。
  • 如果currentCustomer 为空,这仍会将null 推入processed。您的意思是将$currentCustomer 包裹在$ifNull 中吗?
猜你喜欢
  • 2018-07-29
  • 1970-01-01
  • 2021-10-25
  • 1970-01-01
  • 1970-01-01
  • 2021-12-16
  • 1970-01-01
  • 2012-09-28
  • 2022-08-03
相关资源
最近更新 更多