I have a recursive structure in one of my MongoDB collections, like so:
{
"id": String,
"date": Date,
"text": String
"replies": [{
// Same structure as above
}]
}
I need to update a document in this hierarchy by adding in a reply to a nested document. The guarantees are as follows:
The object to reply to is guaranteed to exist.
The path to the object to which a reply is to be posted is known (i.e., we have a sequence of the _id attributes to navigate).
The recursion depth is not bounded.
Searching SO gives me the following relevant questions:
Querying, 2013 - The recommendation was to switch to a graph DB.
Updating, 2015 - The recommendation was to use the $[] operator.
Based on the latter, my attempt was:
await commCollection.update(
{ _id: topLevel['_id'] },
{
$push: {
'replies.$[].$[comment_arr].replies': {
id: commentId,
text: comment,
date,
replies: []
}
}
},
{ arrayFilters: [{ 'comment_arr.id': responseTo }]}
);
where topLevel is the root document, responseTo is the id attribute of the object to add the reply to. This however, does not seem to work. What am I doing wrong, and how do I achieve this?
Update: An example below. Here's an example of a document from MongoDB Atlas:
{
"_id": {
"$oid": "605fdb8d933c5f50b4d2225e"
},
"id": "mr9pwc",
"username": "recrimination-logistical",
"upvotes": {
"$numberInt": "0"
},
"downvotes": {
"$numberInt": "0"
},
"text": "Top-level comment",
"date": {
"$date": {
"$numberLong": "1616894861300"
}
},
"replies": [{
"id": "dflu1h",
"username": "patrolman-hurt",
"upvotes": {
"$numberInt": "0"
},
"downvotes": {
"$numberInt": "0"
},
"text": "Testing reply level 1!",
"date": {
"$date": {
"$numberLong": "1618387567042"
}
},
"replies": [] // ----> want to add a reply here
}]
}
I've indicated where we want a reply added. responseTo in this case would be dflu1h.
Just need to remove $[] positional
Field name in arrayFilters must be an alphanumeric string beginning with a lowercase letter, Change comment_arr to any alphabets only like commentArr, remove special characters
await commCollection.update(
{ _id: topLevel['_id'] },
{
$push: {
"replies.$[commentArr].replies": {
id: commentId,
text: comment,
date,
replies: []
}
}
},
{ arrayFilters: [{ "commentArr.id": responseTo }] }
)
Playground
Related
In the database, I have documents like the following
Ticket {
"eventHistory": [
{
"event": "CREATED",
"timestamp": "aa-bb-cccc"
},
{
"event": "ASSIGNED",
"timestamp": "ii-jj-kkkk"
},
...
{
"event": "CLOSED",
"timestamp": "xx-yy-zzzz"
}
]
}
I would like to add a closedAt field to the relevant Tickets, getting the value from the eventHistory array's last element. The resultant document would look like the following
Ticket {
"eventHistory": [
{
"event": "CREATED",
"timestamp": "aa-bb-cccc"
},
{
"event": "ASSIGNED",
"timestamp": "ii-jj-kkkk"
},
...
{
"event": "CLOSED",
"timestamp": "xx-yy-zzzz"
}
],
"closedAt": "xx-yy-zzzz"
}
The following pipeline allows me to use the entire object that's present as the eventHistory array's last element.
db.collection.updateMany(
<query>,
[
"$set": {
"closedAt": {
"$arrayElemAt": [
"$eventHistory",
-1
]
}
}
]
...
)
But I want to use only the timestamp field; not the entire object.
Please help me adjust (and/or improve) the pipeline.
One option to fix your query is:
db.collection.updateMany(
<query>,
[
{
$set: {
"Ticket.closedAt": {
$last: "$Ticket.eventHistory.timestamp"
}
}
}
])
See how it works on the playground example
But note that you assume that last item is a closing one. Is this necessarily the case? Otherwise you can validate it.
Given a collection of documents similar to the following document
{
"description": "janeetjack",
"name": "Pocog bistro janeetjack"
}
where name is a unique field
How do I update all existing documents and add additional fields so it looks like this
{
"userDetails":{
"description": "janeetjack",
"name": "Pocog bistro janeetjack"
},
"userLikes":{
"likes": "food",
"plays": "ball"
},
}
You need to run set and unset on all docs with updateMany
Playground
db.collection.update({},
{
"$set": {
"userDetails.description": "$description",
"userDetails.name": "$name",
"userLikes.like": "abs"
},
"$unset": {
description: 1,
name: 1
}
})
I was wondering, is there a way in MongoDB to get all the documents from one collection excluding those that exists in another, without using aggregation.
Let me explain myself more;
I have a collection of "Users", and another collection where there is an array of objects.
Something like this:
User
}
"_id": "61e6bbe49d7efc57f895ab50",
"name": "User 1"
},
{
"_id": "61e6b9239d7efc57f895ab02",
"name": "User 2"
},
{
"_id": "61cae6176d0d9a36efd8f190",
"name": "User 3"
},
{
"_id": "61cade886d0d9a36efd8f11a",
"name": "User 4"
},
The other collection looks like this:
{
users: [
{
user: {
"_id": "61e6b9239d7efc57f895ab02",
"name": "User 2",
},
...
},
{
user: {
"_id": "61cae6176d0d9a36efd8f190",
"name": "User 3",
},
...
},
],
},
I would like to get all the users in "array 1" excluding those in "array 2".
So the result should be:
[
{
"_id": "61e6b9239d7efc57f895ab02",
"name": "User 1"
},
{
"_id": "61cae6176d0d9a36efd8f190",
"name": "User 4"
},
],
So, is there a way to do that without the need to do aggregation.
you can use the $and like this:
const found = await User.find({
$and: [
{ _id: { $in: array1 } },
{ _id: { $nin: array2 } }
]
});
For anyone looking for a solution, if you want to add new element to an array of objects, where you, somehow, forgot to initialize the _id field, remember you have to, because mongo adds that field automatically (to use it for filtering/indexation).
All you have to do, is to write something like this;
const data = Data.create({
_id: mongoose.Types.ObjectId.createFromHexString(uid),
users: [{ user: userId, initiator: true, _id: userId}],
})
and for the searching, you write what you always write;
const found = await User.find({ _id: { $nin: data.users } });
I'm new in here and I've just started to uses mongodb recently. I have a problem, I'm trying to update a collection's item in a given model if the item exists or update it if not.
I tried the following code but it's not working:
const res = await this.userModel.findByIdAndUpdate({
_id: user.id,
// 'devices.name': 'postman2'
}, {
$addToSet: { 'devices.[]': { name: 'postman2', generatedSecret: generatedSecret } }
}, { new: true, upsert: true }).exec();
My document looks like this:
{
"_id": {
"$oid": "5e9c6ffe9463db1594a74bec"
},
"email": "john.doe#mail.com",
"logins": [{
"provider": "email",
"secret": "sha1$e3d548b5$1$6f1b28e6b7cef47ca27b4e55ddbb6a5b8bc6b0ce"
}],
"devices": [{
"_id": {
"$oid": "5e9ddc9adb866666845bf86b"
},
"name": "postman",
"generatedSecret": "$2b$10$7fmneXGS1FjKyPXBa2Ea1erQfXF3ALjylIxOhetA9yxc3S95K4LVO"
}],
"applications": [{
"applicationId": "5e9c8b7c9463db1594a74bed"
}]
}
I want to update that devices item which matchs the query conditions. But if there isn't any matched result, then insert a new item with searched parameters.
Is possible to do this in one command?
Thanks!
I'm searching for documents that contain a particular _id in the contacts array in the document. So here's the structure:
{
"_id": ObjectId("505c2daea9d397f2260001cd"),
"contacts": [
{
"userGroupId": ObjectId("50422b53743d6c7c0e000000"),
"userId": ObjectId("5061f8c66096eee07d000008")
},
{
"userGroupId": ObjectId("505bf9476096ee990200000e"),
"userId": ObjectId("505c2daea9d397f2260001ce")
},
{
"userGroupId": ObjectId("50422b75743d6c700e000004"),
"userId": ObjectId("506cff736096ee1e26000384")
},
{
"userGroupId": ObjectId("50422b66743d6c6b0e000000"),
"userId": ObjectId("505c2daea9d397f2260001cf")
},
{
"userGroupId": ObjectId("5050e86aa9d3977b67000000"),
"userId": ObjectId("506494ef6096ee021f000064")
},
{
"userGroupId": ObjectId("50422b53743d6c7c0e000000"),
"userId": ObjectId("504d72246096ee2348000008")
},
{
"userId": ObjectId("50735e8e6096ee7c510002b9"),
"userGroupId": ObjectId("5046c73e6096ee1b77000001")
}
]
}
Here's a second document:
{
"_id": ObjectId("505c2da0a9d397f2260000b7"),
"contacts": [
{
"userGroupId": ObjectId("50422b66743d6c6b0e000000"),
"userId": ObjectId("505c2da0a9d397f2260000b8")
},
{
"userId": ObjectId("5061f8c66096eee07d000008"),
"userGroupId": ObjectId("50422b53743d6c7c0e000000")
},
{
"userId": ObjectId("50735e8e6096ee7c510002b9"),
"userGroupId": ObjectId("5046c73e6096ee1b77000001")
}
]
}
You'll notice that both documents have a userId of ObjectId("50735e8e6096ee7c510002b9") in them. I run this command:
db.collection.find({ 'contacts':
{$elemMatch: { userId: ObjectId("50735e8e6096ee7c510002b9") } }
});
Which should (I think) return both documents. But it only returns the second document. Not the first. I have also tried:
db.collection.find({'contacts.userId': ObjectId("50735e8e6096ee7c510002b9") });
which does the same thing as the $elemMatch query.
I'm probably missing something really elementary, but if you guys could offer some advice I'd really appreciate it.
This was a problem with the application layer it turns out, not Mongo. One of the records was numerically keyed, the other was not, I just had been working with Rock Mongo which seems to key all the arrays.
Thanks for your help JohnnyHK