删除在任何mongodb数组中找到的字段 [英] Remove field found in any mongodb array

查看:98
本文介绍了删除在任何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",并保留所有其他字段?

谢谢

解决方案

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

首先,我不确定嵌套数组中显示的元素是数组中的 元素,还是实际上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" : [ ]
}

那这些都在做什么?

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

但是仅仅因为它们是困难并不意味着不可能.只是您需要了解 $elemMatch :

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

这是基本的find()示例,该示例根据 <外部数组的c5> 条件使用另一个是一个奇异的谓词.像这样:

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

只是行不通.都不会:

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

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

这是与需要处理的文档"相匹配的"<查询>谓词",其余内容实际上用于确定要更新的部分".

案例1 中, 内部数组中的$pull ,我们首先需要确定外部数组中的哪些元素包含要更新的数据.这就是"scan.$[a]"使用位置过滤的 运算符.

该运算符基本上将数组中匹配的 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": { 
    "scan.0.0.arrayToDelete": "",
    "scan.0.1.arrayToDelete": "",
    "scan.0.2.arrayToDelete": "",
    "scan.0.3.arrayToDelete": "",  // My fingers are tired :-<
  } }

这是所有位置$[] 运算符.此命令比所有索引" 而不用像上面显示的 horrible 例那样输入每个字符的方法.

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

可以仍然使用arrayFilters

请注意,起初"b.arrayToDelete"可能不是您所期望的,但是考虑到"scan.$[a].$[b]中的位置,这确实是有道理的,因为从b开始,元素名称将通过点符号"到达如图所示.实际上,在两种情况下都如此.再说一次, $unset 仅适用于字段,所以实际上不需要选择标准.

案例3 .嗯,这很简单,因为如果您删除内容后不需要将其他任何内容保留在数组中(即 $pull 作为显示在这里,在这些条件下,您会得到:

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

或使用 $unset :

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

两者显然都不可取.因此,如果arrayToDelete字段完全是其中的 内容,这就是您的理由,那么全部删除只是将数组替换为空数组.或者确实 $unset 整个文档属性.

但是请注意,所有这些奇特的事物" ( $set )要求您至少必须具有MongoDB 3.6可用才能使用此功能.

如果您仍在运行比该版本更旧的MongoDB(从撰写之日起,您实际上不应该这样做,因为从该日期起仅5个月内您的官方支持就用光了),那么您可以在<实际上,a href ="https://stackoverflow.com/questions/4669178/how-to-update-multiple-array-elements-in-mongodb">如何更新mongodb中的多个数组元素.

I have a document looking like this:

{
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]
        },
    ],

]

}

We only want to delete any instance of the arrayToDelete that is in a dictionary nested in a list of lists in "scan".

Currently, I've been using

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

to delete the array. However, this only deletes the first (0.0) as you would imagine.

Is it possible to iterate over the scan array and the nested array to delete "arrayToDelete", and keep all other fields?

Thanks

解决方案

So I asked a question in the comments but you seem to have walked away, so I guess I just answer the three possible cases I see.

To begin with, I'm not certain if the elements shown within the nested arrays are the only elements within the array or in fact if arrayToDelete is the only field present in those elements. So basically I need to abstract a little and include that case:

{
    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"
            },
        ],
    ]
}

Case 1 - Remove the inner array elements where the Field is present

This would use the $pull operator since that is what removes array elements entirely. You do this in modern MongoDB with a statement like this:

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

That alters all matching documents like this:

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

So every element that contained that field is now removed.

Case 2 - Just remove the matched field from the inner elements

This is where you use $unset. It's just a little different to the "hard indexed" version you were doing:

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

Which alters all matched documents to be:

{
        "_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"
                        }
                ]
        ]
}

So everything is still there, but just the identified fields have been removed from each inner array document.

Case 3 - You Actually wanted to remove "Everything" in the array.

Which is really just a simple case of using $set and wiping everything that was there before:

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

Where the results should pretty well be expected:

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

So what are these all doing?

The very first thing you should see is the query predicate. This is generally a good idea to make sure you are not matching and even "attempting" to have update conditions met on documents which do not even contain data with the pattern you are intending to update. Nested arrays are difficult at best, and where practical you really should avoid them, as what you often "really mean" is actually represented in a singular array with additional attributes representing what you "think" the nesting is actually doing for you.

But just because they are hard does not mean impossible. It's just that you need to understand $elemMatch:

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

That's the basic find() example, which matches based on the $elemMatch condition for the outer array uses another $elemMatch in order to match another condition in the inner array. Even though this "appears" to be a singular predicate. Something like:

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

Just will not work. Neither would:

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

With the "double dot" .. because that's basically just not valid.

That's the query predicate to match "documents" that need to be processed, but the rest applies to actually determine *what parts to update".

In the Case 1 in order to $pull from the inner array, we first need to be able to identify which elements of the outer array contain the data to update. That's what the "scan.$[a]" thing is doing using the positional filtered $[<identifier>] operator.

That operator basically transposes the matched indices ( so many of them ) in the array to another predicate which is defined in the third section of the update style commands with the arrayFilters section. This section basically defines the conditions to be met from the perspective of the named identifier.

In this case out "identifier" is named a, and that is the prefix used in the arrayFilters entry:

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

Taken in context with the actual update statement part:

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

Then from the perspective of the "a" being the identifier for the outer array element first inward from "scan", then same conditions apply as for the original query predicate but from "within" the first $elemMatch statement. So you can basically think of this as a "query within a query" from the perspective of already "looking inside" the content of each outer element.

By the same token the $pull acts much like a "query within a query" in that it's own arguments are also applied from the perspective of the element of the array. Therefore just the arrayToDelete field existing instead of:

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

But that's all specific to $pull, and other things have different cases:

The Case 2 looks at where you want to just $unset the named field. Seems pretty easy as you just name the field, right? Well not exactly since the following is clearly not right from what we know earlier:

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

And of course noting array indexes for everything is just a pain:

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

This is the reason for the positional all $[] operator. This one is a little more "brute force" than the positional filtered $[<identifier>] in that instead of matching another predicate provided within arrayFilters, what this simply does is apply to everything within the array contents at that "index". It's basically a way of in fact saying "all indexes" without typing every single one out like the horrible case shown above.

So it's not for all cases, but it's certainly well suited to an $unset since that has a very specific path naming which does not matter of course if that path does not match every single element of the array.

You could still use an arrayFilters and a positional filtered $[<identifier>], but here it would be overkill. Plus it does not hurt to demonstrate the other approach.

But of course it probably is worth understanding how exactly that statement would look, so:

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

Noting there that the "b.arrayToDelete" may not be what you expect at first, but given the positioning in "scan.$[a].$[b] it really should make sense as from the b the element name would be reached via "dot notation" just as shown. And in fact in both cases. Yet again, an $unset would only apply to the named field anyway, so the selection criteria really is not required.

And Case 3. Well it's quite simple in that if you don't need to keep anything else in the array after removing this content ( ie a $pull where fields matching this were the only things in there, or an $unset for that matter ), then simply don't mess around with anything else and just wipe the array.

This is an important distinction if you consider that as per the point to clarify whether the documents with the named field where the only elements within the nested arrays, and indeed that the named key was the only thing present in the documents.

With the reasoning being that using $pull as shown here and under those conditions you would get:

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

Or with the $unset:

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

Both of which are clearly not desirable. So it stands yo reason if the arrayToDelete field was the only content that was in there at all, then the most logical way to remove all is simply to replace the array with an empty one. Or indeed $unset the whole document property.

Note however that all these "fancy things" ( with the exception of the $set of course ) require that you must have MongoDB 3.6 at least available in order to use this functionality.

In the event you are still running an older version MongoDB than that ( and as of the date of writing, you really should not be since your official support runs out in just 5 months from this date ) then other existing answers on How to Update Multiple Array Elements in mongodb are actually for you.

这篇关于删除在任何mongodb数组中找到的字段的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆