简体   繁体   English

猫鼬:在嵌套数组中查找和更新

[英]Mongoose: Find and Update in nested array

So, i have this schema 所以,我有这个模式

Purchases Schema: 购买模式:

const purchasesSchema = new Schema({
  date: String,
  status: String,
  product: { type: Schema.Types.ObjectId, ref: 'Product' }
})

Product Schema: 产品架构:

const productSchema = new Schema({
  product_name: String,
  transaction: [{
    price: Number,
    purchase: { type: Schema.Types.ObjectId, ref: 'Purchases' }
  }]

The algorithm, before making a purchase must create a product name, then create product transaction in purchase with bond transaction.purchase. 该算法在进行购买之前必须先创建一个产品名称,然后再通过bond transaction.purchase在购买中创建产品交易。

Purchase document sample: 采购文件样本:

[{
  "_id": "5ac0b7cab7924a1710398c9e",
  "date": "01/04/2018",
  "status": "Not Paid",
  "product": {
    "_id": "5ac0b7b1b7924a1710398c9a",
    "product_name": "Milk",
    "transactions": [
      {
      "_id": "5ac0b7c9b7924a1710398c9b",
      "price": 5000,
      }
    ],
  }
}]

Expected document with purchase id binding in transactions 交易中具有购买ID绑定的预期凭证

[{
  "_id": "5ac0b7cab7924a1710398c9e",
  "date": "01/04/2018",
  "status": "Not Paid",
  "product": {
    "_id": "5ac0b7b1b7924a1710398c9a",
    "product_name": "Milk",
    "transactions": [
      {
      "_id": "5ac0b7c9b7924a1710398c9b",
      "price": 5000,
      "purchase": "the id"
      }
    ],
  }
}]

So far i had tried, in case i want to push transaction to already product name: 到目前为止,我已经尝试过了,以防万一我想将交易推送到已经存在的产品名称中:

    const newTransaction = {
        product_price: req.body.product_price
      }
      Product.findOneAndUpdate({ _id: req.body.product_id }, { $push: { transaction: newTransaction } }, { new: true }, (err, data) => {
        const TRANSACTION_ID = data.transaction[data.transaction.length -1]._id

        const newPurchase = new Purchase({
          date: moment(req.body.date).format('DD/MM/YYYY'),
          status: 'Not Paid',
          product: req.body.product_id,
        })
        newPurchase.save((err, data) => {
          if (err) throw err
          const PURCHASES_ID = data._id
          Product.findOneAndUpdate({ 'transactions._id': TRANSCATION_ID }, { $set: { transactions: { purchase: PURCHASES_ID } } }, (err, data) => { if (err) return handleError(err) })

The problem how to push Purchase id as 'transactions.purchase' in the nested array product schema. 在嵌套数组产品架构中如何将“购买ID”作为“ transactions.purchase”推送的问题。 Thank you. 谢谢。

Solved from this answer https://stackoverflow.com/a/23577266/5834822 从这个答案中解决了https://stackoverflow.com/a/23577266/5834822

General Scope and Explanation 一般范围和说明

There are a few things wrong with what you are doing here. 您在这里所做的事情有些错误。 Firstly your query conditions. 首先,您的查询条件。 You are referring to several _id values where you should not need to, and at least one of which is not on the top level. 您所指的是几个_id值,这些值您不需要,并且其中至少一个不在顶层。

In order to get into a "nested" value and also presuming that _id value is unique and would not appear in any other document, you query form should be like this: 为了获得“嵌套”值,并还假定_id值是唯一的并且不会出现在任何其他文档中,您查询的表单应如下所示:

Model.update(
    { "array1.array2._id": "123" },
    { "$push": { "array1.0.array2.$.answeredBy": "success" } },
    function(err,numAffected) {
       // something with the result in here
    }
);

Now that would actually work, but really it is only a fluke that it does as there are very good reasons why it should not work for you. 现在可以实际使用,但实际上只是really幸,因为有很好的理由说明它不适合您。

The important reading is in the official documentation for the positional $ operator under the subject of "Nested Arrays". 重要的阅读内容是“ $嵌套数组”主题下的位置$运算符的官方文档。 What this says is: 这是什么意思:

The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value 位置$运算符不能用于遍历一个以上数组的查询,例如遍历嵌套在其他数组中的数组的查询,因为$占位符的替换是单个值

Specifically what that means is the element that will be matched and returned in the positional placeholder is the value of the index from the first matching array. 具体而言,这意味着将被匹配并在位置占位符中返回的元素是来自第一个匹配数组的索引值。 This means in your case the matching index on the "top" level array. 在您的情况下,这意味着“顶级”数组上的匹配索引。

So if you look at the query notation as shown, we have "hardcoded" the first ( or 0 index ) position in the top level array, and it just so happens that the matching element within "array2" is also the zero index entry. 因此,如果您查看所示的查询符号,我们已经对顶层数组中的第一个 (或0索引)位置进行了“硬编码”,并且碰巧“ array2”中的匹配元素也是零索引条目。

To demonstrate this you can change the matching _id value to "124" and the result will $push an new entry onto the element with _id "123" as they are both in the zero index entry of "array1" and that is the value returned to the placeholder. 为了说明这一点,您可以将匹配的_id值更改为“ 124”,结果将把新条目$push到具有_id “ 123”的元素上,因为它们都在“ array1”的零索引条目中,并且这是返回的值到占位符。

So that is the general problem with nesting arrays. 这就是嵌套数组的普遍问题。 You could remove one of the levels and you would still be able to $push to the correct element in your "top" array, but there would still be multiple levels. 您可以删除其中一个级别,仍然可以将$push到“顶部”数组中的正确元素,但是仍然会有多个级别。

Try to avoid nesting arrays as you will run into update problems as is shown. 尝试避免嵌套数组,因为您将遇到如图所示的更新问题。

The general case is to "flatten" the things you "think" are "levels" and actually make theses "attributes" on the final detail items. 通常的情况是“平化”您“认为”的事物是“层次”,并实际上将这些归因于最终细节项目。 For example, the "flattened" form of the structure in the question should be something like: 例如,问题中结构的“扁平化”形式应类似于:

 {
   "answers": [
     { "by": "success", "type2": "123", "type1": "12" }
   ]
 }

Or even when accepting the inner array is $push only, and never updated: 甚至当接受内部数组时,它只是$push ,并且从未更新:

 {
   "array": [
     { "type1": "12", "type2": "123", "answeredBy": ["success"] },
     { "type1": "12", "type2": "124", "answeredBy": [] }
   ]
 }

Which both lend themselves to atomic updates within the scope of the positional $ operator 两者都适合在$运算符范围内进行原子更新


MongoDB 3.6 and Above MongoDB 3.6及更高版本

From MongoDB 3.6 there are new features available to work with nested arrays. 从MongoDB 3.6开始,有一些新功能可用于嵌套数组。 This uses the positional filtered $[<identifier>] syntax in order to match the specific elements and apply different conditions through arrayFilters in the update statement: 为了匹配特定元素并通过update语句中的arrayFilters应用不同的条件,这使用位置过滤的$[<identifier>]语法:

Model.update(
  {
    "_id": 1,
    "array1": {
      "$elemMatch": {
        "_id": "12","array2._id": "123"
      }
    }
  },
  {
    "$push": { "array1.$[outer].array2.$[inner].answeredBy": "success" }
  },
  {
    "arrayFilters": [{ "outer._id": "12" },{ "inner._id": "123" }] 
  }
)

The "arrayFilters" as passed to the options for .update() or even .updateOne() , .updateMany() , .findOneAndUpdate() or .bulkWrite() method specifies the conditions to match on the identifier given in the update statement. 传递给.update()甚至.updateOne() .updateMany() .findOneAndUpdate().bulkWrite()方法的选项的"arrayFilters"指定与update语句中给出的标识符匹配的条件。 Any elements that match the condition given will be updated. 符合给定条件的任何元素都将被更新。

Because the structure is "nested", we actually use "multiple filters" as is specified with an "array" of filter definitions as shown. 因为结构是“嵌套的”,所以我们实际上使用“多个过滤器”,如通过过滤器定义的“数组”指定的那样,如图所示。 The marked "identifier" is used in matching against the positional filtered $[<identifier>] syntax actually used in the update block of the statement. 标记的“标识符”用于与语句的更新块中实际使用的位置过滤的$[<identifier>]语法匹配。 In this case inner and outer are the identifiers used for each condition as specified with the nested chain. 在这种情况下, innerouter是嵌套链所指定的每个条件所使用的标识符。

This new expansion makes the update of nested array content possible, but it does not really help with the practicality of "querying" such data, so the same caveats apply as explained earlier. 这个新的扩展使嵌套数组内容的更新成为可能,但是它对“查询”此类数据的实用性并没有真正的帮助,因此如前所述适用相同的警告。

You typically really "mean" to express as "attributes", even if your brain initially thinks "nesting", it's just usually a reaction to how you believe the "previous relational parts" come together. 即使您的大脑最初认为是“嵌套”,您通常也确实会“平均”地表示为“属性”,这通常只是对您认为“先前的关系部分”融合在一起的一种反应。 In reality you really need more denormalization. 实际上,您确实需要更多的非规范化。

Also see How to Update Multiple Array Elements in mongodb , since these new update operators actually match and update "multiple array elements" rather than just the first , which has been the previous action of positional updates. 另请参阅如何在mongodb中更新多个数组元素 ,因为这些新的更新运算符实际上匹配并更新“多个数组元素”,而不仅仅是第一个 (位置更新的先前动作)。

NOTE Somewhat ironically, since this is specified in the "options" argument for .update() and like methods, the syntax is generally compatible with all recent release driver versions. 注意有点讽刺意味的是,由于这是在.update()和类似方法的“ options”参数中指定的,因此该语法通常与所有最新的发行版驱动程序兼容。

However this is not true of the mongo shell, since the way the method is implemented there ( "ironically for backward compatibility" ) the arrayFilters argument is not recognized and removed by an internal method that parses the options in order to deliver "backward compatibility" with prior MongoDB server versions and a "legacy" .update() API call syntax. 但是,对于mongo shell并非如此,因为在那里实现该方法的方式(“具有讽刺意味的是向后兼容”), arrayFilters解析选项的内部方法来识别和删除arrayFilters参数,以实现“向后兼容性”以及以前的MongoDB服务器版本和“旧版” .update() API调用语法。

So if you want to use the command in the mongo shell or other "shell based" products ( notably Robo 3T ) you need a latest version from either the development branch or production release as of 3.6 or greater. 因此,如果要在mongo shell或其他“基于shell的”产品(尤其是Robo 3T)中使用该命令,则需要开发分支或生产版本中的3.6或更高版本。

See also positional all $[] which also updates "multiple array elements" but without applying to specified conditions and applies to all elements in the array where that is the desired action. 另请参见positional all $[] ,它还会更新“多个数组元素”,但不适用于指定条件,并且适用于所需动作的数组中的所有元素。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM