首页 > 解决方案 > 按多个条件从嵌套数组中删除对象

问题描述

我在 mongoDB 中有这个模式文档:

{
    "_id": UUID("cf397865-c000-4f51-8959-1aae84769706"),
    "CreationDateTime": ISODate("2016-05-06T05:09:14.589Z"),
    "WKT": "",
    "Distributions": [{
            "_id": UUID("bb95bedb-4baa-4ada-90b1-0d763e70ebfe"),
            "DeliveryType": 1,
            "DistributionData": [{
                    "Key": "Topic",
                    "Value": "Topics",
                    "Children": null
                }, {
                    "Key": null,
                    "Value": null,
                    "Children": null
                }, {
                    "Key": "Message",
                    "Value": "test",
                    "Children": null
                }
            ],
            "Schedules": [
                ISODate("2016-05-06T05:09:56.988Z")
            ]
        }
    ],
}

我想清理我的数据库。所以为了我决定删除空对象。DistributionData如何删除所有三个属性都具有空值的对象:

 {
  "Key": null,
  "Value": null,
  "Children": null
 }.

我写了这个查询:

db.Events.update(
    {},
    {$pull:
        {
            results: {
                $elemMatch: {
                    "Distributions[0].DistributionData" : {$in:[null]}
                }
            }
        }
     },
     { multi: true }
)

当我执行此查询时,什么也没有发生!我知道$elemMatch是错误的.. 现在我怎样才能删除所有字段为空的 json 对象DistributionData?我读 了这个这个,但让我感到困惑......

编辑

我写了这个查询:

db.Events.update(
    {},
    {$pull:
        {
            Distributions : {
                DistributionData:{
                $elemMatch: {
                    "Key" : null
                }
              }
            }
        }
     },
     { multi: true }
)

此查询将完全删除 Distributions 中的对象,该DistributionData数组具有带有 null 键的对象:

结果

{
"_id": UUID("cf397865-c000-4f51-8959-1aae84769706"),
"CreationDateTime": ISODate("2016-05-06T05:09:14.589Z"),
"WKT" : "",
"Distributions" : [],...

标签: mongodbmongodb-query

解决方案


您可以$pull通过简单地执行以下操作从“外部数组”中删除“所有内部元素”来“第一次匹配”:

db.Events.updateMany(
  {
    "Distributions.DistributionData": {
      "$elemMatch": {
        "Key": null,
        "Value": null,
        "Children": null
      }
    }
  },
  {
    "$pull": {
      "Distributions.$.DistributionData": { 
        "Key": null,
        "Value": null,
        "Children": null
      }
    }
  }
)

"Distributions"如果您在数组中只有一个条目,或者至少其中一个条目具有匹配条件的子数组条目,那很好。这就是位置$运算符如何与所有版本的 MongoDB 一起工作。

如果数据在“外部”"Distributions"数组中有“多个”匹配项,那么如果您有 MongoDB 3.6,则可以应用位置过滤$[<identifier>]运算符来修改所有匹配的条目:

db.Events.updateMany(
  {
    "Distributions.DistributionData": {
      "$elemMatch": {
        "Key": null,
        "Value": null,
        "Children": null
      }
    }
  },
  {
    "$pull": {
      "Distributions.$[element].DistributionData": { 
        "Key": null,
        "Value": null,
        "Children": null
      }
    }
  },
  {
    "arrayFilters": [
      { "element.DistributionData": {
        "$elemMatch": {
          "Key": null,
          "Value": null,
          "Children": null
        }
      }}
    ]
  }
)

在这种情况下,该arrayFilters选项定义了一个条件,我们通过该条件匹配“外部”数组中的条目,以便这实际上可以应用于匹配的所有内容。

或者实际上,由于$pull本质上本身就具有这些条件,那么在这种情况下,您可以交替使用位置 all$[]运算符:

db.Event.updateMany(
  {
    "Distributions.DistributionData": {
      "$elemMatch": {
        "Key": null,
        "Value": null,
        "Children": null
      }
    }
  },
  {
    "$pull": {
      "Distributions.$[].DistributionData": { 
        "Key": null,
        "Value": null,
        "Children": null
      }
    }
  }
)

null任何一种情况都会通过删除带有所有键的内部项目来更改问题中的文档:

{
        "_id" : UUID("cf397865-c000-4f51-8959-1aae84769706"),
        "CreationDateTime" : ISODate("2016-05-06T05:09:14.589Z"),
        "WKT" : "",
        "Distributions" : [
                {
                        "_id" : UUID("bb95bedb-4baa-4ada-90b1-0d763e70ebfe"),
                        "DeliveryType" : 1,
                        "DistributionData" : [
                                {
                                        "Key" : "Topic",
                                        "Value" : "Topics",
                                        "Children" : null
                                },
                                {
                                        "Key" : "Message",
                                        "Value" : "test",
                                        "Children" : null
                                }
                        ],
                        "Schedules" : [
                                ISODate("2016-05-06T05:09:56.988Z")
                        ]
                }
        ]
}

“查询”条件全部$elemMatch用于文档选择。这实际上是位置$运算符所必需的,以便获得用于“第一次匹配”的“位置索引”。虽然这实际上不是位置过滤$[<identifier>]位置 all$[]运算符的“要求”,但它仍然很有用,因此您甚至不必考虑更新的文档,这些文档与后面的更新条件不匹配$pullarrayFilters选项。

至于$pull本身,这里的条件实际上适用于“每个”数组元素,因此不需要$elemMatch在该操作中,因为我们已经在查看“元素”级别。

第三个示例表明,位置 all$[]运算符可以简单地使用$pull考虑到每个“内部”数组元素的这些条件,并且仅适用于所有“外部”数组元素。所以位置过滤$[<identifier>]表达式的实际点是“仅”处理那些实际匹配“内部”条件的“外部”数组元素。因此,为什么我们$elemMatch在考虑匹配每个“内部”数组元素时使用。


如果您实际上至少没有 MongoDB 3.6,那么您正在使用第一种形式,并且可能会重复该形式,直到更新最终不再返回修改后的文档,表明没有更多符合条件的元素。

在How to Update Multiple Array Elements in mongodb 中有关于“替代方案”的更详细的文章,但只要您的数据适合初始情况或者您确实有可用的 MongoDB 3.6,那么这是正确的接近这里。


如果您想查看 MongoDB 3.6 新语法的完整效果。这是我在这里用来验证更新语句的问题中对文档的更改:

{
    "_id" : UUID("cf397865-c000-4f51-8959-1aae84769706"),
    "CreationDateTime" : ISODate("2016-05-06T05:09:14.589Z"),
    "WKT" : "",
    "Distributions" : [
            {
                    "_id" : UUID("bb95bedb-4baa-4ada-90b1-0d763e70ebfe"),
                    "DeliveryType" : 1,
                    "DistributionData" : [
                            {
                                    "Key" : "Topic",
                                    "Value" : "Topics",
                                    "Children" : null
                            },
                            {
                                    "Key" : null,
                                    "Value" : null,
                                    "Children" : null
                            },
                            {
                                    "Key" : "Message",
                                    "Value" : "test",
                                    "Children" : null
                            },
                            {
                                    "Key" : null,
                                    "Value" : null,
                                    "Children" : null
                            }
                    ],
                    "Schedules" : [
                            ISODate("2016-05-06T05:09:56.988Z")
                    ]
            },
            {
                    "_id" : UUID("bb95bedb-4baa-4ada-90b1-0d763e70ebfe"),
                    "DeliveryType" : 1,
                    "DistributionData" : [
                            {
                                    "Key" : "Topic",
                                    "Value" : "Topics",
                                    "Children" : null
                            },
                            {
                                    "Key" : null,
                                    "Value" : null,
                                    "Children" : null
                            },
                            {
                                    "Key" : "Message",
                                    "Value" : "test",
                                    "Children" : null
                            },
                            {
                                    "Key" : null,
                                    "Value" : null,
                                    "Children" : null
                            }
                    ],
                    "Schedules" : [
                            ISODate("2016-05-06T05:09:56.988Z")
                    ]
            }
    ]
}

这基本上复制了一些“外部”和“内部”条目,以显示语句如何删除所有null值。

注意 arrayFilters在“选项”参数中指定.update()和类似的方法,语法通常与所有最新版本的驱动程序版本兼容,甚至与 MongoDB 3.6 版本之前的版本兼容。

然而,这不适用于mongoshell,因为该方法在那里实现的方式(“具有讽刺意味的是,为了向后兼容”),该arrayFilters参数不会被解析选项的内部方法识别和删除,以便提供与先前的“向后兼容性” MongoDB 服务器版本和“遗留” .update()API 调用语法。

因此,如果您想在mongoshell 或其他“基于 shell”的产品(特别是 Robo 3T)中使用该命令,您需要来自开发分支或生产版本 3.6 或更高版本的最新版本。

值得注意的是,Robo 3T 仍然基于 MongoDB 3.4 shell。因此,即使连接到功能强大的 MongoDB 3.6 实例,这些选项也不会从该程序传递到服务器。建议仅使用外壳和受支持的产品,尽管还有一些其他产品没有相同的限制。


推荐阅读