Move data from inside nested array - mongodb

I have inserted multiple documents in my Mongo database incorrectly. I have accidentally nested the data inside another data object:
{
"_id": "5cdfda8ddc5cf00031fd3949",
"payload": {
"timestamp": "2019-05-18T10:12:29.896Z",
"data": {
"data": {
"name": 10,
"age": 10,
}
}
},
"__v": 0
}
I would like the document to not have the extra data object. So I would like it to look like this:
{
"_id": "5cdfda8ddc5cf00031fd3949",
"payload": {
"timestamp": "2019-05-18T10:12:29.896Z",
"data": {
"name": 10,
"age": 10,
}
},
"__v": 0
}
Is there a way in Mongo for me to update all the documents that have 2 data objects to just have one like shown above?

Alas, you cannot do this with one database request. You have to loop over all documents programmatically, set the new data and update them in the database.

You could use the aggregation framework, which won't let you update in place, but you could use the $out operator to write the results to a new collection, if that's an option.
db.collection.aggregate([
{
$project: {
__v : 1,
"payload.timestamp" : 1,
"payload.data" : "$payload.data.data"
},
},
{
"$out": "newCollection"
}
])
Or if you have a mixture of docs with correct format and docs with incorrect format, you can use the $cond operator to determine the correct output:
db.collection.aggregate([
{
$project: {
__v : 1,
"payload.timestamp" : 1,
"payload.data" : {
$cond: [
{ $ne : [ "$payload.data.data", undefined]},
"$payload.data.data",
"$payload.data"
]}
}
},
{
"$out": "newCollection"
}
])

Related

MongoDB use array field's element to $set a new field of the document

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.

MongoDB delete embedded documents through array of Ids

I am working on a Node.js application that is using a MongoDB database with Mongoose. I've been stuck in this thing and didn't come up with the right query.
Problem:
There is a collection named chats which contain embedded documents (rooms) as an array of objects. I want to delete these embedded documents (rooms) through Ids which are in the array.
{
"_id": "ObjectId(6138e2b55c175846ec1e38c5)",
"type": "bot",
"rooms": [
{
"_id": "ObjectId(6138e2b55c145846ec1e38c5)",
"genre": "action"
},
{
"_id": "ObjectId(6138e2b545c145846ec1e38c5)",
"genre": "adventure"
}
]
},
{
"_id": "ObjectId(6138e2b55c1765846ec1e38c5)",
"type": "person",
"rooms": [
{
"_id": "ObjectId(6138e2565c145846ec1e38c5)",
"genre": "food"
},
{
"_id": "ObjectId(6138e2b5645c145846ec1e38c5)",
"genre": "sport"
}
]
},
{
"_id": "ObjectId(6138e2b55c1765846ec1e38c5)",
"type": "duo",
"rooms": [
{
"_id": "ObjectId(6138e21c145846ec1e38c5)",
"genre": "travel"
},
{
"_id": "ObjectId(6138e35645c145846ec1e38c5)",
"genre": "news"
}
]
}
I am converting my array of ids into MongoDB ObjectId so I can use these ids as match criteria.
const idsRoom = [
'6138e21c145846ec1e38c5',
'6138e2565c145846ec1e38c5',
'6138e2b545c145846ec1e38c5',
];
const objectIdArray = idsRoom.map((s) => mongoose.Types.ObjectId(s));
and using this query for the chat collection. But it is deleting the whole document and I only want to delete the rooms embedded document because the ids array is only for the embedded documents.
Chat.deleteMany({ 'rooms._id': objectIdArray }, function (err) {
console.log('Delete successfully')
})
I really appreciate your help on this issue.
You have to use $pull operator in a update query like this:
This query look for documents where exists the _id into rooms array and use $pull to remove the object from the array.
yourModel.updateMany({
"rooms._id": {
"$in": [
"6138e21c145846ec1e38c5",
"6138e2565c145846ec1e38c5",
"6138e2b545c145846ec1e38c5"
]
}
},
{
"$pull": {
"rooms": {
"_id": {
"$in": [
"6138e21c145846ec1e38c5",
"6138e2565c145846ec1e38c5",
"6138e2b545c145846ec1e38c5"
]
}
}
}
})
Example here.
Also you can run your query without the query parameter (in update queries the first object is the query) like this and result is the same. But is better to indicate mongo the documents using this first object.

MongoDB moving array of sub-documents to it's own collection

I'm looking to move an array of subdocuments into a collection of it's own keyed by the owner id. Currently, my collection is formed like this:
"_id": ObjectId("123"),
"username": "Bob Dole",
"logins": [{
"_id": ObjectId("abc123"),
"date": ISODate("2016")
}, {
"_id": ObjectId("def456"),
"date": ISODate("2016")
}]
I'm looking for the best way to write a script that would loop over each user, and move each item in the logins array to it's own "logins" collection, as follows:
{
"_id": ObjectId("abc123"),
"_ownerId": ObjectId("123"),
"date": ISODate("2016")
}
{
"_id": ObjectId("def567"),
"_ownerId": ObjectId("123"),
"date": ISODate("2016")
}
When the script ends, I'd like the login array to be removed entirely from all users.
this query will create new collection using aggregation framework
to see how it looks - just remove $out pipeline phase
db.thinking.aggregate([
{
$unwind:"$logins"
},{
$project:{
_id:"$logins._id",
_ownerId:"$_id",
date:"$logins.date"
}
},
{
$out: "newCollection"
}
])
to delete array records - as suggested in comment:
db.thinking.update({},{ "$unset": { "logins": "" } },{ "multi": true })

Criteria Morphia MongoDB

I have a collection like this:
{
"_id": {
"$oid": "53f34ef8ec10d6fa97dcc34b"
},
"editions": [
{
"number": 1,
...
},
{
"number": 2,
...
},
...
]
}
I want filter results of my query by some number.
I tried
criterias.add(query.criteria("editions.number").equal(paramNumber));
And
query.filter("editions.number =", paramNumber)
However I just received all collection, when I pass paramNumber equals 2. What I want is receive the following result:
{
"_id": {
"$oid": "53f34ef8ec10d6fa97dcc34b"
},
"editions": [
{
"number": 2,
...
}
]
}
What am I doing wrong?
You can't receive partial arrays like that. You'll get back with the full document/object or just the fields you've specified in a projection.

Mongo - Querying inside array

I have this db structure
{
"_id": 107,
"standard": {"name": "building",
"item": [{"code": 151001,
"quantity": 10,
"delivered": 8,
"um": "kg" },
{"code": 151001,
"quantity": 20,
"delivered": 6,
"um": "kg" }]
}
}
And i would like to find all the objects that have code:151001 and just show the delivered field.
For example it would show something like this:
{delivered: 8}
{delivered: 6}
So far i got this query, but it does not show exactly what i want:
db.test.find(
{
"standard.item.code": 151001
}
).pretty()
Since your items are in an array, your best approach will be to use the Aggregation Framework for this.
Example code:
db.test.aggregate(
// Find matching documents (could take advantage of an index)
{ $match: {
"standard.item.code" : 151001,
}},
// Unpack the item array into a stream of documents
{ $unwind: "$standard.item" },
// Filter to the items that match the code
{ $match: {
"standard.item.code" : 151001,
}},
// Only show the delivered amounts
{ $project: {
_id: 0,
delivered: "$standard.item.delivered"
}}
)
Results:
{
"result" : [
{
"delivered" : 8
},
{
"delivered" : 6
}
],
"ok" : 1
}
You'll notice there are two $match steps in the aggregation. The first is to match the documents including that item code. After using $unwind on the array, the second $match limits to the items with that code.