删除在任何mongodb数组中找到的字段

时间:2019-04-01 07:00:46

标签: mongodb

我有一个看起来像这样的文件:

{
field: 'value',
field2: 'value',
scan: [
    [
        {
            dontDeleteMe: 'keepMe',
            arrayToDelete: [0,1,2]
        },
        {
            dontDeleteMe: 'keepMe',
            arrayToDelete: [0,1,2]
        },
        {
            dontDeleteMe: 'keepMe',
            arrayToDelete: [0,1,2]
        },
        {
            dontDeleteMe: 'keepMe',
            arrayToDelete: [0,1,2]
        },
    ],
    [
        {
            dontDeleteMe: 'keepMe',
            arrayToDelete: [0,1,2]
        },
        {
            dontDeleteMe: 'keepMe',
            arrayToDelete: [0,1,2]
        },
        {
            dontDeleteMe: 'keepMe',
            arrayToDelete: [0,1,2]
        },
        {
            dontDeleteMe: 'keepMe',
            arrayToDelete: [0,1,2]
        },
    ],
    [
        {
            dontDeleteMe: 'keepMe',
            arrayToDelete: [0,1,2]
        },
        {
            dontDeleteMe: 'keepMe',
            arrayToDelete: [0,1,2]
        },
        {
            dontDeleteMe: 'keepMe',
            arrayToDelete: [0,1,2]
        },
        {
            dontDeleteMe: 'keepMe',
            arrayToDelete: [0,1,2]
        },
    ],

]

}

我们只想删除嵌套在“扫描”列表列表中的字典中的arrayToDelete的任何实例。

当前,我一直在使用

 update({}, {$unset: {"scans.0.0.arrayToDelete":1}}, {multi: true})

删除数组。但是,这只会删除您想像的第一个(0.0)

是否可以遍历扫描数组和嵌套数组以删除“ arrayToDelete”,并保留所有其他字段?

谢谢

2 个答案:

答案 0 :(得分:2)

所以我在评论中问了一个问题,但是您似乎已经走开了,所以我想我只是回答了我看到的三种可能的情况。

首先,我不确定嵌套数组中显示的元素是否是数组中的 元素,或者实际上是arrayToDelete是这些元素中出现的 字段。所以基本上我需要抽象一点并包括这种情况:

{
    field: 'value',
    field2: 'value',
    scan: [
        [
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {   somethingToKeep: 1 },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
        ],
        [
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {   somethingToKeep: 1 },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
        ],
        [
            {   somethingToKeep: 1 },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
        ],
    ]
}

情况1-删除存在该字段的内部数组元素

这将使用$pull运算符,因为这完全是删除数组元素。您可以在现代的MongoDB中使用以下语句执行此操作:

db.collection.updateMany(
  { "scan": {
    "$elemMatch": {
      "$elemMatch": {
        "arrayToDelete": { "$exists": true }
      }
    }
  } },
  {
    "$pull": {
      "scan.$[a]": { "arrayToDelete": { "$exists": true } }
    }
  },
  { "arrayFilters": [
      {  "a": { "$elemMatch": { "arrayToDelete": { "$exists": true } } } }
    ]
  }
)

这样会更改所有匹配的文档:

{
        "_id" : ObjectId("5ca1c36d9e31550a618011e2"),
        "field" : "value",
        "field2" : "value",
        "scan" : [
                [
                        {
                                "somethingToKeep" : 1
                        }
                ],
                [
                        {
                                "somethingToKeep" : 1
                        }
                ],
                [
                        {
                                "somethingToKeep" : 1
                        }
                ]
        ]
}

因此,包含该字段的每个元素都将被删除。

情况2-只需从内部元素中删除匹配的字段

您在这里使用$unset。这与您正在执行的“硬索引” 版本略有不同:

db.collection.updateMany(
  { "scan": {
    "$elemMatch": {
      "$elemMatch": {
        "arrayToDelete": { "$exists": true }
      }
    }
  } },
  { "$unset": { "scan.$[].$[].arrayToDelete": ""  } }
)

将所有匹配的文档更改为:

{
        "_id" : ObjectId("5ca1c4c49e31550a618011e3"),
        "field" : "value",
        "field2" : "value",
        "scan" : [
                [
                        {
                                "anotherField" : "a"
                        },
                        {
                                "somethingToKeep" : 1
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        }
                ],
                [
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "somethingToKeep" : 1
                        },
                        {
                                "anotherField" : "a"
                        }
                ],
                [
                        {
                                "somethingToKeep" : 1
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        }
                ]
        ]
}

所以一切仍然存在,但是从每个内部数组文档中仅删除了已标识的字段。

情况3-您实际上想删除阵列中的“所有内容”。

实际上只是使用$set并清除之前的所有内容的简单情况:

db.collection.updateMany(
  { "scan": {
    "$elemMatch": {
      "$elemMatch": {
        "arrayToDelete": { "$exists": true }
      }
    }
  } },
  { "$set": { "scan": []  } }
)

应该可以很好地预期结果:

{
        "_id" : ObjectId("5ca1c5c59e31550a618011e4"),
        "field" : "value",
        "field2" : "value",
        "scan" : [ ]
}

那么这些都在做什么?

您应该首先看到的是查询谓词。通常,这是一个很好的主意,以确保您不匹配甚至“尝试” 以使文档上的更新条件得到满足,这些文档甚至不包含要更新的模式的数据。嵌套数组充其量是困难,在实际情况中,您应该避免使用它们,因为您通常“真正的意思” 实际上是用单个数组表示的,而其他属性则表示您“想” 嵌套实际上是为您服务的。

但是仅仅因为它们是 hard 并不意味着不可能。只是您需要了解$elemMatch

db.colelction.find(
  { "scan": {
    "$elemMatch": {
      "$elemMatch": {
        "arrayToDelete": { "$exists": true }
      }
    }
  }}
)

这是基本的find()示例,它根据 outer 数组的$elemMatch条件进行匹配,并使用另一个$elemMatch来匹配内部阵列。即使这个“出现” 是一个奇异的谓词。像这样:

"scan.arrayToDelete": { "$exists": true }

只是行不通。都不会:

"scan..arrayToDelete": { "$exists": true }

使用“双点” ..,因为这基本上是无效的。

这是与需要处理的“文档”相匹配的“ <查询>谓词”,其余内容则用于确定“需要更新的部分”。

案例1 中,为了从内部数组中$pull开始,我们首先需要能够识别外部元素数组包含要更新的数据。这就是"scan.$[a]"使用positional filtered $[<identifier>]运算符所做的事情。

该运算符基本上将数组中匹配的 indices (所以其中的许多转置到另一个谓词它是在update样式命令的第三部分中与arrayFilters部分一起定义的。本节从命名标识符的角度基本上定义了要满足的条件。

在这种情况下,“标识符”被命名为a,这是arrayFilters条目中使用的前缀:

  { "arrayFilters": [
      {  "a": { "$elemMatch": { "arrayToDelete": { "$exists": true } } } }
    ]
  }

在上下文中与实际的 update语句部分相同:

  {
    "$pull": {
      "scan.$[a]": { "arrayToDelete": { "$exists": true } }
    }
  },

然后从"a"作为外部数组元素的标识符(从"scan"开始向内)的角度出发,然后采用与原始查询相同的条件谓词,但来自第一 $elemMatch语句的“内” 。因此,从已经“向内看” 每个外部元素的内容的角度,您基本上可以将其视为“查询中的查询”。

通过相同的标记,$pull的作用与“查询中的查询” 相似,因为从数组元素的角度来看,它也是自变量。因此,仅存在arrayToDelete字段,而不是:

  // This would be wrong! and do nothing :(
  {
    "$pull": {
      "scan.$[a]": { "$elemMatch": { "arrayToDelete": { "$exists": true } } }
    }
  }

但这都是$pull所特有的,其他情况也有所不同:

第2种情况将查看您只想$unset命名字段的位置。您只需为字段命名就很容易了,对吧?完全不正确,因为以下内容显然不符合我们先前所知道的:

  { "$unset": { "scan.arrayToDelete": ""  } } // Not right :(

当然,要注意所有内容的数组索引只是一个痛苦:

  { "$unset": { 
    "scan.0.0.arrayToDelete": "",
    "scan.0.1.arrayToDelete": "",
    "scan.0.2.arrayToDelete": "",
    "scan.0.3.arrayToDelete": "",  // My fingers are tired :-<
  } }

这是positional all $[]运算符的原因。这比positional filtered $[<identifier>]“蛮力” 多一点,因为与其匹配arrayFilters中提供的另一个谓词适用于该“索引”处数组内容中的 一切 。实际上,这实际上是一种说出“所有索引” 而不用像上面显示的 horrible 例那样键入每个字符的方法。

因此,并非所有情况都适用,但它肯定非常适合$unset,因为它具有非常具体的路径命名,这当然不重要如果该路径与数组的每个元素都不匹配。

可以仍然使用arrayFilterspositional filtered $[<identifier>],但是这太过分了。此外,演示另一种方法也无妨。

但是,当然值得理解 该语句的外观,所以:

db.collection.updateMany(
  { "scan": {
    "$elemMatch": {
      "$elemMatch": {
        "arrayToDelete": { "$exists": true }
      }
    }
  } },
  { "$unset": { "scan.$[a].$[b].arrayToDelete": ""  } },
  {
    "arrayFilters": [
      { "a": { "$elemMatch": { "arrayToDelete": { "$exists": true } } } },
      { "b.arrayToDelete": { "$exists": true } },
    ]
  }
)

请注意,"b.arrayToDelete"可能一开始并不是您所期望的,但是考虑到"scan.$[a].$[b]中的位置,从b开始确实应该有元素名称如图所示,通过“点符号”。实际上,在两种情况下。同样,$unset仍然只适用于命名字段,因此确实不需要选择标准。

案例3 。嗯,这很简单,如果您删除内容后不需要 将其他任何内容保留在数组中(即$pull,与之匹配的字段仅是 $unset),然后就不用弄乱其他任何东西,而只需擦除数组

如果您要考虑这一点,这是一个重要的区别,以澄清带有命名字段的文档是否嵌套数组中的 only 元素,实际上命名的键是文档中存在的 内容。

原因是,如此处所示,在这些条件下使用$pull,您将获得:

{
        "_id" : ObjectId("5ca321909e31550a618011e6"),
        "field" : "value",
        "field2" : "value",
        "scan" : [
            [ ],
            [ ],
            [ ]
        ]
}

或使用$unset

{
        "_id" : ObjectId("5ca322bc9e31550a618011e7"),
        "field" : "value",
        "field2" : "value",
        "scan" : [
            [{ }, { }, { }, { }],
            [{ }, { }, { }, { }],
            [{ }, { }, { }, { }]
        ]
}

这两者显然是不希望的。因此,如果arrayToDelete字段完全是其中的 内容,这就是您的理由,那么删除所有< / em>只是将数组替换为空数组。甚至$unset整个文档属性。

但是请注意,所有这些“奇特的事物” (当然是$set除外)都要求您必须至少具有MongoDB 3.6可用为了使用此功能。

如果您仍在运行比该版本更旧的MongoDB(从撰写之日起,您实际上不应该这样做,因为从此日期起仅5个月您的官方支持就用光了),然后在{ {3}}实际上是给你的。

答案 1 :(得分:0)

我已尝试使用给定的示例文档及其工作原理,您必须使用$[]来实现:

db.collectionName.(update({},{$unset: {
   "scan.$[].$[].arrayToDelete": 1
}})