How can I merge two documents in MongoDB - mongodb

I have two documents in one collection.
{id: 1, list_data: [1, 2, 4, 5]}
{id: 1, list_data: [2, 5, 8, 9]}
I want merge those data into one document.
{id: 1, list_data: [1, 2, 4, 5, 8, 9]}
How can I do this job?
Please help me.
Thanks.

According to MongoDB documentation
Aggregation operations group values from multiple documents together,
and can perform a variety of operations on the grouped data to return
a single result.
Please refer the aggregation query as mentioned below .
db.collection.aggregate(
// Pipeline
[
// Stage 1
{
$unwind: {
path:'$list_data'
}
},
// Stage 2
{
$group: {
_id:{id:'$id'},
list_data:{$addToSet:'$list_data'}
}
},
// Stage 3
{
$project: {
'_id.id':1,
"list_data":1
}
},
]
);
In above query document is processed through multiple stages of aggregation pipeline

Related

MongoDB aggregate query for values in an array

So I have data that looks like this:
{
_id: 1,
ranking: 5,
tags: ['Good service', 'Clean room']
}
Each of these stand for a review. There can be multiple reviews with a ranking of 5. The tags field can be filled with up to 4 different tags.
4 tags are: 'Good service', 'Good food', 'Clean room', 'Need improvement'
I want to make a MongoDB aggregate query where I say 'for each ranking (1-5) give me the number of times each tag occurred for each ranking.
So an example result might look like this, _id being the ranking:
[
{ _id: 5,
totalCount: 5,
tags: {
goodService: 1,
goodFood: 3,
cleanRoom: 1,
needImprovement: 0
},
{ _id: 4,
totalCount: 7,
tags: {
goodService: 0,
goodFood: 2,
cleanRoom: 3,
needImprovement: 0
},
...
]
Having trouble with the counting the occurrences of each tag. Any help would be appreciated
You can try below aggregation.
db.colname.aggregate([
{"$unwind":"$tags"},
{"$group":{
"_id":{
"ranking":"$ranking",
"tags":"$tags"
},
"count":{"$sum":1}
}},
{"$group":{
"_id":"$_id.ranking",
"totalCount":{"$sum":"$count"},
"tags":{"$push":{"tags":"$_id.tags","count":"$count"}}
}}
])
To get the key value pair instead of array you can replace $push with $mergeObjects from 3.6 version.
"tags":{"$mergeObjects":{"$arrayToObject":[[["$_id.tags","$count"]]]}}

use $addToSet to add an item inside an inner-array, to a specific field

So, I have this collection. I am trying to add responseId of value 9 to the array of responsesIds, where the requestId is 2.
[
{
_id: 1,
partsIds: [
{
requestId: 1,
responesIds: [1, 2, 3, 4]
},
{
requestId: 2,
responesIds: [5, 6, 7, 8]
}
]
}
]
So the pseudocode here would be: $addToSet:{responseIds:9} where requestId:2
How can I achieve this?
So, I found out a solution that works, I haven't given it an extensive test yet, if you find a better solution, I'd be more than happy to listen.
db.collection.update(
{ _id: 1, 'partsIds.requestId': 2 },
{ $addToSet: { 'partsIds.$.responsesIds': 9 } }
);
The $ is called positional operator, which gets the index of the first matching item in the query, (in my case that's index of 1) and you can use it in updating like so.

Matching all elements in an array in an array of arrays with $all in MongoDB

I have a document which consists of documents like these:
{ "f" : [ [ 1, 2, 3], [4, 5, 6] ] } // should match because of [1, 2, 3]
{ "f" : [ [ 2, 1, 3], [4, 5, 6] ] } // should match because of [2, 1, 3]
{ "f" : [ [ 1, 2, 4], [4, 5, 6] ] } // should NOT match
In this collection, I want to match documents, which has an array containing 1, 2 and 3 in one of the arrays of the "f" field.
What I've tried so far:
db.mytest.find({ f: { $elemMatch: { $all: [1,2,3] } } } )
I expect this query to work but I do not understand why it does not. I does not match any documents.
db.mytest.find({ f: { $elemMatch: { $elemMatch: { $all: [1,2,3] } } } })
This also does not work.
db.mytest.find({ f: { $all: [[1,2,3]] } })
This works but the elements have to be in exact order. I want to be able to match when the input array is 2, 1, 3 also. One possible solution must be to always store elements in ascending order and use this query.
db.mytest.find({ f: { $elemMatch: { $elemMatch: { $in: [1, 2, 3] } } } })
This works but it matches all the documents containing any one of 1, 2 or 3. I only want the documents which contain exactly 1, 2 and 3 in a single array.
What is the query I am looking for?
It seems that the operators perform an exact match in case of embedded arrays. Not the most pleasing or optimal solution(you need to test it with the records in your collection), but one way of doing it is via the aggregation pipeline on the server side.
Project an extra field for each document representing the "f" value.
unwind the actual "f" field, so that we can match a single array
elements.
match the documents where "f" contains all the values in the input
array.
project back the temporary field for the original document.
Code:
db.mytest.aggregate([
{$project:{"temp":"$f","f":1}}, // maintain a temporary variable for projection
{$unwind:"$f"}, // f:[1,2,3] f:[4,5,6] become separate records.
{$match:{"f":{$all:[1,2,3]}}}, // use $all to match all the elements.
{$project:{"f":"$temp"}} // project the temporary variable.
])
What about generate all permutations of your array and use in your query?? something like this:
db.mytest.find({$or : [{f: {$all : [[1,2,3]] }}, {f: {$all : [[2,1,3]]}}, {f: {$all: [[3,1,2]]}}]})
Perhaps this is not the efficient way.
You're looking for documents where f contains [1, 2, 3]. Try db.mytest.find({f: {$in: [[1, 2, 3]]}}).
The easiest way to find all the possible permutations is, unfortunately, to construct them in your program (if you're using Python, itertools.permutations will do that for you) and change your $in query to [[1, 2, 3], [2, 1, 3], ...]. It sucks.

Conditional $inc in a nested MongoDB array

My database looks like this:
{
_id: 1,
values: [ 1, 2, 3, 4, 5 ]
},
{
_id: 2,
values: [ 2, 4, 6, 8, 10 ]
}, ...
I'd like to update every value in every document's nested array ("values") that meets some criterion. For instance, I'd like to increment every value that's >= 4 by one, which ought to yield:
{
_id: 1,
values: [ 1, 2, 3, 5, 6 ]
},
{
_id: 2,
values: [ 2, 5, 7, 8, 11 ]
}, ...
I'm used to working with SQL, where the nested array would be a seperated table connected with a unique ID. I'm a little lost in this new NoSQL world.
Thank you kindly,
This sort of update is not really possible using nested arrays, the reason for this is given in the positional $ operator documentation, and that states that you can only match the first array element for a given condition in the query.
So a statement like this:
db.collection.update(
{ "values": { "$gte": 4 } },
{ "$inc": { "values.$": 1 } }
)
Will not work in the sense that only the "first" array element that was matched would be incremented. So on your first document you would get this:
{ "_id" : 1, "values" : [ 1, 2, 3, 6, 6 ] }
In order to update the values as you are suggesting you would need to iterate the documents and the array elements to produce the result:
db.collecction.find({ "values": { "$gte": 4 } }).forEach(function(doc) {
for ( var i=0; i < doc.values.length; i++ ) {
if ( doc.values[i] >= 4 ) {
doc.values[i]++;
}
}
db.collection.update(
{ "_id": doc._id },
{ "$set": { "values": doc.values } }
);
})
Or whatever code equivalent of that basic concept.
Generally speaking, this sort of update does not lend itself well to a structure that contains elements in an array. If that is really your need, then the elements are better off listed within a separate collection.
Then again, the presentation of this question is more of a "hypothetical" situation without understanding your actual use case for performing this sort of udpate. So if you possibly described what you actually need to do and how your data really looks in another question, then that might get a more meaningful response in terms of the best approach for you to use.

Is there a way to upsert a list with a single query?

I know this question has been asked before, but that's a different scenario.
I'd like to have a collection like this:
{
"_id" : ObjectId("4c28f62cbf8544c60506f11d"),
"pk": 1,
"forums": [{
"pk": 1,
"thread_count": 10,
"post_count": 20,
}, {
"pk": 2,
"thread_count": 5,
"post_count": 24,
}]
}
What I want to do is to upsert a "forum" item, incrementing counters or adding an item if it does not exist.
For example to do something like this (I hope it makes sense):
db.mycollection.update({
"pk": 3,
"forums.pk": 2
}, {
"$inc": {"forums.$.thread_count": 1},
"$inc": {"forums.$.post_count": 1},
}, true)
and have:
{
"_id" : ObjectId("4c28f62cbf8544c60506f11d"),
"pk": 1,
"forums": [{
"pk": 1,
"thread_count": 10,
"post_count": 20,
}, {
"pk": 2,
"thread_count": 5,
"post_count": 24,
}]
},
{
"_id" : ObjectId("4c28f62cbf8544c60506f11e"),
"pk": 3,
"forums": [{
"pk": 2,
"thread_count": 1,
"post_count": 1,
}]
}
I can surely make it in three steps:
Upsert the whole collection with a new item
addToSet the forum item to the list
increment forum item counters with positional operator
That's to say:
db.mycollection.update({pk:3}, {pk:3}, true)
db.mycollection.update({pk:3}, {$addToSet: {forums: {pk:2}}})
db.mycollection.update({pk:3, 'forums.pk': 2}, {$inc: {'forums.$.thread_counter': 1, {'forums.$.post_counter': 1}})
Are you aware of a more efficient way to do it?
TIA, Germano
As you may have discovered, the positional operator cannot be used in upserts:
The positional operator cannot be combined with an upsert since it requires a matching array element. If your update results in an insert then the "$" will literally be used as the field name.
So you won't be able to achieve the desired result in a single query.
You have to separate the creation of the document from the counter update. Your own solution is on the right track. It can be condensed into the following two queries:
// optionally create the document, including the array
db.mycollection.update({pk:3}, {$addToSet: {forums: {pk:2}}}, true)
// update the counters in the array item
db.mycollection.update({pk:3, 'forums.pk': 2}, {$inc: {'forums.$.thread_counter': 1, 'forums.$.post_counter': 1}})