首页 > 解决方案 > MongoDB 数据建模:如何比较子文档字段和根文档字段?

问题描述

在 MongoDB 集合中,我正在尝试对一些具有一些“输入”的硬件“设备”进行建模,并具有以下约束:一次应激活单个输入

这是我用来模拟设备及其输入的(简化的)文档:

{
    "_id" : "device1",
    "inputs" : [ 
        {
            "connectedTo" : "device3",
            "index" : 2
        }, 
        {
            "connectedTo" : null,
            "index" : 3
        }, 
        {
            "connectedTo" : null,
            "index" : 4
        }
    ]
}

现在我正在尝试实现“活动”输入功能。我看到了两种方法:

  1. 在存储活动输入索引的根文档中添加一个“activeInput”字段
  2. 在每个输入子文档上添加一个“活动”标志(布尔)字段,只有一个将活动标志设置为 true 的输入

但是,我在使用这两种方法编写 MongoDB 更新时遇到了一些问题:

使用方法 1:我找不到查询/更新活动输入子文档的方法。这是我尝试的过滤器:

{"inputs.index": {"$eq": "$activeInput"}}
{"$where": "this.inputs.index==this.activeInput"}
{"$expr": {"$eq": ["$inputs.index", "$activeInput"]}}

似乎我们无法比较子文档的字段,或者我找不到如何做到这一点。

使用方法 2:我找不到一种方法来自动更新活动标志,以便一次只有一个输入处于活动状态。对于具有给定索引值的子文档,我需要将活动标志设置为 true,而对于其他子文档,我需要将其设置为 false,但我不知道如何自动执行此操作。

也许我还没有找到正确的方法来模拟“主动”输入功能,所以有人可以帮忙吗?

标签: mongodb

解决方案


采用方法 1

您可以使用更新管道解决此问题,但这会非常复杂:

  • 计算一个newActiveInput包含未来新活动输入的新字段
  • 更新activeInputinputs字段
  • 删除newActiveInput字段
db.getCollection('test').update(
    {"_id" : "device1"},
    [
        {
            "$addFields": {
                "newActiveInput": {
                    "$arrayElemAt": [
                        {
                            "$map": {
                                "input": {
                                    "$filter": {
                                        "input": "$inputs",
                                        "cond": {
                                            "$and": [
                                                {"$ne": ["$$this.index", "$activeInput"]},
                                                {"$eq": ["$$this.connectedTo", null]}
                                            ]
                                        }
                                    }
                                },
                                "as": "input",
                                "in": "$$input.index"
                            },
                        }, 0
                    ]
                }
            }
        },
        {
            "$set": {
                "activeInput": "$newActiveInput",
                "inputs": {     
                   "$concatArrays": [
                       {
                           "$filter": {
                               "input": "$inputs",
                               "cond": {"$lt": ["$$this.index", "$newActiveInput"]}
                           }
                       },
                       [{"connectedTo": "newDevice", "index": "$newActiveInput"}],
                       {
                           "$filter": {
                               "input": "$inputs",
                               "cond": {"$gt": ["$$this.index", "$newActiveInput"]}
                           }
                       },
                   ]
                }
            }
        },
        {
           "$unset": "newActiveInput"
        }
    ]
)

采用方法 2

以下更新将设置active=trueifconnectedTo == "device4"active=falseelse:

db.getCollection('test').update(
  {"_id" : "device1"},
  [
    {
      "$set": {
        "inputs": {     
           "$map": {
             "input": "$inputs",
             "as": "input",
             "in": {
               "$mergeObjects": [
                 "$$input",
                 {
                   "active": { "$cond": [ {"$eq": ["$$input.connectedTo", "device4"]}, true, false ] }
                 }
               ]
             }
           }
        }
      }
    }
  ]
)

另一种解决方案是使用arrayFilters标识要更新的子文档,然后$bit更新以反转当前active值。由于$bit仅适用于整数,因此您需要表示active为整数:

db.getCollection('test').update(
    {
        "_id" : "device1"
    },
    {
        "$bit": {"inputs.$[input].active" : {"xor": NumberInt(1)}}
    },
    {
        "multi": true,
        "arrayFilters": [
            {
                "$or": [
                    {"input.active": NumberInt(1)},
                    {"input.connectedTo": "device5"},
                ]
            }
        ]
    }
)

推荐阅读