如何在 MongoDB 中仅更新数组过滤器的一个匹配项。

huangapple go评论78阅读模式
英文:

How to only update one match of an array filter in mongodb

问题

问题:
我只需要更新可用空位数组中id为"empty"的一个文档。我的先前查询会更新所有匹配的子文档中id为"empty"的内容,这是不好的,如下所示的示例。所以我决定使用聚合,以便我可以添加一个限制阶段,这样我只能更新一个项目,但后来发现我不能使用聚合来更新原始文档。这只留下了使用数组过滤器的选项,只更新其匹配项中的一个/第一个。这种可能吗?我觉得一定有办法只在数组过滤器中更新一个匹配项,如果没有,这绝对是应该添加的。

我的代码:
此代码会更新所有带"empty"的对象

const client = await clientPromise;
const db = client.db();
const query = await db.collection('events').updateOne({
    _id: new ObjectId("6398c34ca67dbe3286452f23"),
    createdBy: new ObjectId("636c1778f1d09191074f9690"),
    "weights.weight": 12
},
{
    $set: {
        "weights.$.spotsAvailable.$[el2]": {
            "name": "Wayne Wrestler",
            "userId": new ObjectId("636c1778f1d09191074f9690")
        }
    }
},
{
    arrayFilters: [{ "el2": { "userId": "empty" } }]
})

示例文档:

事件:

{
  "_id": {
    "$oid": "6398c34ca67dbe3286452f23"
  },
  "name": "test",
  "createdBy": {
    "$oid": "636c1778f1d09191074f9690"
  },
  "description": "testing",
  "date": {
    "$date": {
      "$numberLong": "1645488000000"
    }
  },
  "location": {
    "type": "Point",
    "coordinates": [
      0,
      0
    ]
  },
  "weights": [
    {
      "spotsAvailable": [
        {
          "name": "empty",
          "userId": "empty"
        },
        {
          "name": "empty",
          "userId": "empty"
        },
        {
          "name": "empty",
          "userId": "empty"
        }
      ],
      "weight": 12
    },
    {
      "spotsAvailable": [
        {
          "name": "Wayne Wrestler",
          "userId": {
            "$oid": "636c1778f1d09191074f9690"
          }
        },
        {
          "name": "Wayne Wrestler",
          "userId": {
            "$oid": "636c1778f1d09191074f9690"
          }
        }
      ],
      "weight": 15
    }
  ],
  "eventApplicants": [
    {
      "userId": {
        "$oid": "636c1778f1d09191074f9690"
      },
      "name": "Wayne Wrestler",
      "weight": 12
    }
  ]
}

用户:

{
  "_id": {
    "$oid": "636c1778f1d09191074f9690"
  },
  "name": "Wayne Wrestler",
  "email": "wakywayne80@gmail.com",
  "image": "https://lh3.googleusercontent.com/a/ALm5wu32gXjDIRxncjjQA9I4Yl-sjFH5EWsTlmvdM_0kiw=s96-c",
  "emailVerified": {
    "$date": {
      "$numberLong": "1670864727212"
    }
  },
  "createdEvents": [
    {
      "createdEventName": "test",
      "createdEventDate": {
        "$date": {
          "$numberLong": "1645488000000"
        }
      },
      "createdEventDescription": "testing",
      "createdEventWeights": [
        {
          "weight": "12",
          "filled": [
            false,
            false,
            false
          ]
        },
        {
          "weight": "15",
          "filled": [
            false,
            false
          ]
        }
      ],
      "createdEventId": {
        "$oid": "6398c34ca67dbe3286452f23"
      }
    }
  ],
  "userSignedUpEvents": [],
  "availableWeights": [
    1,
    123
  ],
  "signedUpEvents": [
    {
      "eventId": {
        "$oid": "636c722f67642c30dc5ffc30"
      },
      "eventName": "Utah",
      "eventDate": {
        "$date": {
          "$numberLong": "1667913330000"
        }
      },
      "accepted": false
    },
    {
      "eventId": {
        "$oid": "636c722f67642c30dc5ffc30"
      },
      "eventName": "Utah",
      "eventDate": {
        "$date": {
          "$numberLong": "1667913330000"
        }
      },
      "accepted": false
    },
    {
      "eventId": {
        "$oid": "637ec484ac2d675b30590b47"
      },
      "eventName": "Maybe?",
      "eventDate": {
        "$date": {
          "$numberLong": "1672272000000"
        }
      },
      "accepted": false
    },
    {
      "eventId": {
        "$oid": "636c722f67642c30dc5ffc30"
      },
      "eventName": "Utah",
      "eventDate": {
        "$date": {
          "$numberLong": "1667913330000"
        }
      },
      "accepted": false
    },
    {
      "eventId": {
        "$oid": "638d5274628db2a7bf61df49"
      },
      "eventName": "Eva's",
      "eventDate": {
        "$date": {
          "$numberLong": "1698019200000"
        }
      },
      "accepted": false
    },
    {
      "eventId": {
        "$oid": "636c722f67642c30dc5ffc30"
      },
      "eventName": "Utah",
      "eventDate": {
        "$date": {
          "$numberLong": "1667913330000"
        }
      },
      "accepted": false
    },
    {
      "eventId": {
        "$oid": "6398a922abb5c168ede595fb"
      },
      "eventName": "Nikko's event",
      "eventDate": {
        "$date": {
          "$numberLong": "1670976000000"
        }
      },
      "accepted": false
    },
    {
      "eventId": {


<details>
<summary>英文:</summary>

## Problem: 
I need to only update one document in the spots available array that has an id of &quot;empty&quot;. My previous query was updating all matching sub documents with &quot;empty&quot; as the id; which is no good *Example Below*. So I decided to use aggregation so that I could add a limit stage so that I could only update one item, but come to find out I cannot update the original document with an aggregation. **This leaves the only option to use an array filter that only updates one/first of its matches.** Is this possible? I feel like there has to be a way to only update one match on an array filter and if there isn&#39;t this is definitely something that should be added.

## My code:
*This code updates every object with &quot;empty&quot;*

```javascript
        const client = await clientPromise;
            const db = client.db();
            // const query = db.collection(&#39;events&#39;).aggregate(agg);


            const query = await db.collection(&#39;events&#39;).updateOne({
                _id: new ObjectId(&quot;6398c34ca67dbe3286452f23&quot;),
                createdBy: new ObjectId(&quot;636c1778f1d09191074f9690&quot;),
                &quot;weights.weight&quot;: 12
            },
                {
                    $set: {
                        &quot;weights.$.spotsAvailable.$[el2]&quot;: {
                            &quot;name&quot;: &quot;Wayne Wrestler&quot;,
                            &quot;userId&quot;: new ObjectId(&quot;636c1778f1d09191074f9690&quot;)
                        }
                    }
                },
                {
                    arrayFilters: [{ &quot;el2&quot;: { &quot;userId&quot;: &quot;empty&quot; } }]
                })

Example documents:

Event:

{
  &quot;_id&quot;: {
    &quot;$oid&quot;: &quot;6398c34ca67dbe3286452f23&quot;
  },
  &quot;name&quot;: &quot;test&quot;,
  &quot;createdBy&quot;: {
    &quot;$oid&quot;: &quot;636c1778f1d09191074f9690&quot;
  },
  &quot;description&quot;: &quot;testing&quot;,
  &quot;date&quot;: {
    &quot;$date&quot;: {
      &quot;$numberLong&quot;: &quot;1645488000000&quot;
    }
  },
  &quot;location&quot;: {
    &quot;type&quot;: &quot;Point&quot;,
    &quot;coordinates&quot;: [
      0,
      0
    ]
  },
  &quot;weights&quot;: [
    {
      &quot;spotsAvailable&quot;: [
        {
          &quot;name&quot;: &quot;empty&quot;,
          &quot;userId&quot;: &quot;empty&quot;
        },
        {
          &quot;name&quot;: &quot;empty&quot;,
          &quot;userId&quot;: &quot;empty&quot;
        },
        {
          &quot;name&quot;: &quot;empty&quot;,
          &quot;userId&quot;: &quot;empty&quot;
        }
      ],
      &quot;weight&quot;: 12
    },
    {
      &quot;spotsAvailable&quot;: [
        {
// only one of these should&#39;ve been updated, but both were
          &quot;name&quot;: &quot;Wayne Wrestler&quot;,
          &quot;userId&quot;: {
            &quot;$oid&quot;: &quot;636c1778f1d09191074f9690&quot;
          }
        },
        {
          &quot;name&quot;: &quot;Wayne Wrestler&quot;,
          &quot;userId&quot;: {
            &quot;$oid&quot;: &quot;636c1778f1d09191074f9690&quot;
          }
        }
      ],
      &quot;weight&quot;: 15
    }
  ],
  &quot;eventApplicants&quot;: [
    {
      &quot;userId&quot;: {
        &quot;$oid&quot;: &quot;636c1778f1d09191074f9690&quot;
      },
      &quot;name&quot;: &quot;Wayne Wrestler&quot;,
      &quot;weight&quot;: 12
    }
  ]
}

User:

{
  &quot;_id&quot;: {
    &quot;$oid&quot;: &quot;636c1778f1d09191074f9690&quot;
  },
  &quot;name&quot;: &quot;Wayne Wrestler&quot;,
  &quot;email&quot;: &quot;wakywayne80@gmail.com&quot;,
  &quot;image&quot;: &quot;https://lh3.googleusercontent.com/a/ALm5wu32gXjDIRxncjjQA9I4Yl-sjFH5EWsTlmvdM_0kiw=s96-c&quot;,
  &quot;emailVerified&quot;: {
    &quot;$date&quot;: {
      &quot;$numberLong&quot;: &quot;1670864727212&quot;
    }
  },
  &quot;createdEvents&quot;: [
    {
      &quot;createdEventName&quot;: &quot;test&quot;,
      &quot;createdEventDate&quot;: {
        &quot;$date&quot;: {
          &quot;$numberLong&quot;: &quot;1645488000000&quot;
        }
      },
      &quot;createdEventDescription&quot;: &quot;testing&quot;,
      &quot;createdEventWeights&quot;: [
        {
          &quot;weight&quot;: &quot;12&quot;,
          &quot;filled&quot;: [
            false,
            false,
            false
          ]
        },
        {
          &quot;weight&quot;: &quot;15&quot;,
          &quot;filled&quot;: [
            false,
            false
          ]
        }
      ],
      &quot;createdEventId&quot;: {
        &quot;$oid&quot;: &quot;6398c34ca67dbe3286452f23&quot;
      }
    }
  ],
  &quot;userSignedUpEvents&quot;: [],
  &quot;availableWeights&quot;: [
    1,
    123
  ],
  &quot;signedUpEvents&quot;: [
    {
      &quot;eventId&quot;: {
        &quot;$oid&quot;: &quot;636c722f67642c30dc5ffc30&quot;
      },
      &quot;eventName&quot;: &quot;Utah&quot;,
      &quot;eventDate&quot;: {
        &quot;$date&quot;: {
          &quot;$numberLong&quot;: &quot;1667913330000&quot;
        }
      },
      &quot;accepted&quot;: false
    },
    {
      &quot;eventId&quot;: {
        &quot;$oid&quot;: &quot;636c722f67642c30dc5ffc30&quot;
      },
      &quot;eventName&quot;: &quot;Utah&quot;,
      &quot;eventDate&quot;: {
        &quot;$date&quot;: {
          &quot;$numberLong&quot;: &quot;1667913330000&quot;
        }
      },
      &quot;accepted&quot;: false
    },
    {
      &quot;eventId&quot;: {
        &quot;$oid&quot;: &quot;637ec484ac2d675b30590b47&quot;
      },
      &quot;eventName&quot;: &quot;Maybe?&quot;,
      &quot;eventDate&quot;: {
        &quot;$date&quot;: {
          &quot;$numberLong&quot;: &quot;1672272000000&quot;
        }
      },
      &quot;accepted&quot;: false
    },
    {
      &quot;eventId&quot;: {
        &quot;$oid&quot;: &quot;636c722f67642c30dc5ffc30&quot;
      },
      &quot;eventName&quot;: &quot;Utah&quot;,
      &quot;eventDate&quot;: {
        &quot;$date&quot;: {
          &quot;$numberLong&quot;: &quot;1667913330000&quot;
        }
      },
      &quot;accepted&quot;: false
    },
    {
      &quot;eventId&quot;: {
        &quot;$oid&quot;: &quot;638d5274628db2a7bf61df49&quot;
      },
      &quot;eventName&quot;: &quot;Eva&#39;s&quot;,
      &quot;eventDate&quot;: {
        &quot;$date&quot;: {
          &quot;$numberLong&quot;: &quot;1698019200000&quot;
        }
      },
      &quot;accepted&quot;: false
    },
    {
      &quot;eventId&quot;: {
        &quot;$oid&quot;: &quot;636c722f67642c30dc5ffc30&quot;
      },
      &quot;eventName&quot;: &quot;Utah&quot;,
      &quot;eventDate&quot;: {
        &quot;$date&quot;: {
          &quot;$numberLong&quot;: &quot;1667913330000&quot;
        }
      },
      &quot;accepted&quot;: false
    },
    {
      &quot;eventId&quot;: {
        &quot;$oid&quot;: &quot;6398a922abb5c168ede595fb&quot;
      },
      &quot;eventName&quot;: &quot;Nikko&#39;s event&quot;,
      &quot;eventDate&quot;: {
        &quot;$date&quot;: {
          &quot;$numberLong&quot;: &quot;1670976000000&quot;
        }
      },
      &quot;accepted&quot;: false
    },
    {
      &quot;eventId&quot;: {
        &quot;$oid&quot;: &quot;6398a922abb5c168ede595fb&quot;
      },
      &quot;eventName&quot;: &quot;Nikko&#39;s event&quot;,
      &quot;eventDate&quot;: {
        &quot;$date&quot;: {
          &quot;$numberLong&quot;: &quot;1670976000000&quot;
        }
      },
      &quot;accepted&quot;: false
    },
    {
      &quot;eventId&quot;: {
        &quot;$oid&quot;: &quot;6398c34ca67dbe3286452f23&quot;
      },
      &quot;eventName&quot;: &quot;test&quot;,
      &quot;eventDate&quot;: {
        &quot;$date&quot;: {
          &quot;$numberLong&quot;: &quot;1645488000000&quot;
        }
      },
      &quot;accepted&quot;: false
    },
    {
      &quot;eventId&quot;: {
        &quot;$oid&quot;: &quot;6398c34ca67dbe3286452f23&quot;
      },
      &quot;eventName&quot;: &quot;test&quot;,
      &quot;eventDate&quot;: {
        &quot;$date&quot;: {
          &quot;$numberLong&quot;: &quot;1645488000000&quot;
        }
      },
      &quot;accepted&quot;: false
    },
    {
      &quot;eventId&quot;: {
        &quot;$oid&quot;: &quot;6398c34ca67dbe3286452f23&quot;
      },
      &quot;eventName&quot;: &quot;test&quot;,
      &quot;eventDate&quot;: {
        &quot;$date&quot;: {
          &quot;$numberLong&quot;: &quot;1645488000000&quot;
        }
      },
      &quot;accepted&quot;: false
    },
    {
      &quot;eventId&quot;: {
        &quot;$oid&quot;: &quot;6398c34ca67dbe3286452f23&quot;
      },
      &quot;eventName&quot;: &quot;test&quot;,
      &quot;eventDate&quot;: {
        &quot;$date&quot;: {
          &quot;$numberLong&quot;: &quot;1645488000000&quot;
        }
      },
      &quot;accepted&quot;: false
    }
  ]
}

I have tried:

  1. Pluging in variables without the new ObjectId syntax
  2. Plugin in variables with the new ObjectId syntax
  3. Using the exact same hardcoded values that I got from copying the aggregation code from compass for the node driver

All of these either don't work or result in every subdocument with "empty" getting filled

答案1

得分: 2

以下是您要翻译的内容:

"一种选择是使用管道进行更新:

由于这是一个双重嵌套数组,最好分两步进行 - 内部和外部

  1. 首先创建要在 weights 数组中替换的“外部”项目,并将其命名为 newItem。可以使用 $reduce 来计算它,这允许我们在循环中操作内部数组。
  2. 使用 $map$cond 替换 weights 数组中的相关项目
db.collection.update(
  {_id: ObjectId("6398c34ca67dbe3286452f23"), "weights.weight": 12},
  [
    {$set: {
      newItem: {$reduce: {
          input: {$getField: {
              input: {$first: {
                  $filter: {
                    input: "$weights",
                    as: "item",
                    cond: {$eq: ["$$item.weight", 12]}
                  }
              }},
              field: "spotsAvailable"
          }},
          initialValue: [],
          in: {$concatArrays: [
              "$$value",
              {$cond: [
                  {$and: [
                      {$eq: ["$$this.userId", "empty"]},
                      {$not: {$in: [ObjectId("636c1778f1d09191074f9690"), "$$value.userId"]}}
                  ]},
                  [{
                      name: "Wayne Wrestler",
                      userId: ObjectId("636c1778f1d09191074f9690")
                  }],
                  ["$$this"]
                ]}
            ]}
      }}
  }},
  {$set: {
      weights: {$map: {
          input: "$weights",
          in: {$cond: [
              {$eq: ["$$this.weight", 12]},
              {$mergeObjects: [
                  "$$this",
                  {spotsAvailable: "$newItem"}
              ]},
              "$$this"
          ]}
      }},
      newItem: "$$REMOVE"
  }}
])

playground 示例 上查看它是如何工作的。"

英文:

One option is to use update with pipeline:

Since this is a double nested array, it is easier to do it in two steps - internal and external

  1. First create the "external" item to replace in weights array and call it newItem. It is calculated using $reduce which allow us to manipulate the internal array while looping on it.
  2. Replace the relevant item on weights array with our newItem using $map with $cond
db.collection.update(
{_id: ObjectId(&quot;6398c34ca67dbe3286452f23&quot;), &quot;weights.weight&quot;: 12},
[
{$set: {
newItem: {$reduce: {
input: {$getField: {
input: {$first: {
$filter: {
input: &quot;$weights&quot;,
as: &quot;item&quot;,
cond: {$eq: [&quot;$$item.weight&quot;, 12]}
}
}},
field: &quot;spotsAvailable&quot;
}},
initialValue: [],
in: {$concatArrays: [
&quot;$$value&quot;,
{$cond: [
{$and: [
{$eq: [&quot;$$this.userId&quot;, &quot;empty&quot;]},
{$not: {$in: [ObjectId(&quot;636c1778f1d09191074f9690&quot;), &quot;$$value.userId&quot;]}}
]},
[{
name: &quot;Wayne Wrestler&quot;,
userId: ObjectId(&quot;636c1778f1d09191074f9690&quot;)
}],
[&quot;$$this&quot;]
]}
]}
}}
}},
{$set: {
weights: {$map: {
input: &quot;$weights&quot;,
in: {$cond: [
{$eq: [&quot;$$this.weight&quot;, 12]},
{$mergeObjects: [
&quot;$$this&quot;,
{spotsAvailable: &quot;$newItem&quot;}
]},
&quot;$$this&quot;
]}
}},
newItem: &quot;$$REMOVE&quot;
}}
])

See how it works on the playground example

答案2

得分: 1

你可以首先对weights进行$unwind以便更容易处理。使用$reduce迭代weights.spotsAvailable数组,并使用一个复合对象来存储结果和一个指示是否已更新的标志。最后,使用结果进行$merge回到原始文档中。

英文:

You can first $unwind the weights for easier processing first. Use $reduce to iterate through the weights.spotsAvailable array and use a compound object to store the result and a flag to indicate whether it is updated or not. Finally use the result to $merge back to the original document.

db.collection.aggregate([
  {
    $match: {
      &quot;_id&quot;: ObjectId(&quot;6398c34ca67dbe3286452f23&quot;),
      createdBy: ObjectId(&quot;636c1778f1d09191074f9690&quot;),
      &quot;weights.weight&quot;: 12,
      &quot;weights.spotsAvailable.userId&quot;: &quot;empty&quot;
    }
  },
  {
    &quot;$unwind&quot;: &quot;$weights&quot;
  },
  {
    &quot;$addFields&quot;: {
      &quot;results&quot;: {
        &quot;$reduce&quot;: {
          &quot;input&quot;: &quot;$weights.spotsAvailable&quot;,
          &quot;initialValue&quot;: {
            result: [],
            updated: false
          },
          &quot;in&quot;: {
            &quot;$cond&quot;: {
              &quot;if&quot;: {
                $and: [
                  {
                    $eq: [
                      false,
                      &quot;$$value.updated&quot;
                    ]
                  },
                  {
                    $eq: [
                      &quot;empty&quot;,
                      &quot;$$this.userId&quot;
                    ]
                  }
                ]
              },
              &quot;then&quot;: {
                result: {
                  &quot;$concatArrays&quot;: [
                    &quot;$$value.result&quot;,
                    [
                      {
                        &quot;name&quot;: &quot;Wayne Wrestler&quot;,
                        &quot;userId&quot;: ObjectId(&quot;636c1778f1d09191074f9690&quot;)
                      }
                    ]
                  ]
                },
                updated: true
              },
              &quot;else&quot;: {
                result: {
                  &quot;$concatArrays&quot;: [
                    &quot;$$value.result&quot;,
                    [
                      &quot;$$this&quot;
                    ]
                  ]
                },
                updated: &quot;$$value.updated&quot;
              }
            }
          }
        }
      }
    }
  },
  {
    $set: {
      &quot;weights.spotsAvailable&quot;: &quot;$results.result&quot;,
      &quot;results&quot;: &quot;$$REMOVE&quot;
    }
  },
  {
    $group: {
      _id: &quot;$_id&quot;,
      &quot;name&quot;: {
        $first: &quot;$name&quot;
      },
      &quot;createdBy&quot;: {
        $first: &quot;$createdBy&quot;
      },
      &quot;description&quot;: {
        $first: &quot;$description&quot;
      },
      &quot;date&quot;: {
        $first: &quot;$date&quot;
      },
      &quot;location&quot;: {
        $first: &quot;$location&quot;
      },
      &quot;weights&quot;: {
        $push: &quot;$weights&quot;
      },
      &quot;eventApplicants&quot;: {
        $first: &quot;$eventApplicants&quot;
      }
    }
  },
  {
    &quot;$merge&quot;: {
      &quot;into&quot;: &quot;collection&quot;,
      &quot;on&quot;: &quot;_id&quot;
    }
  }
])

Mongo Playground

huangapple
  • 本文由 发表于 2023年1月5日 02:01:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/75009484.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定