mongoDb $in with aggregate query - mongodb

How do I write an $in query along with aggregate in mongoDB? I want to write an equivalent mongoDB query for the SQL below
SELECT name,count(something) from collection1
where name in (<<list of Array>>) and cond1 = 'false'
group by name

The equivalent mongo query follows:
db.collection1.aggregate([
{ "$match": {
"name": { "$in": arrayList },
"cond1": "false"
} },
{ "$group": {
"_id": "$name",
"count": { "$sum": "$something" }
} }
])

Suppose you have an Schema with field tags
{
tags: ['banana', 'apple', 'orange']
}
and you want find out apple inside tags with aggregate function then
const keywords = "apple"; // req.body.keywords
const filter = { $match : { tags: {$in : [keywords] } }}
Schema.aggregate(filter).then(result=>{
// You can do what you want with result
})

Related

grouping result from multiple queries and aggregating result mongo

I'm new to mongo and I have a document that has an array with the ids of all it's related documents. I need to fetch the document with all it's relateds in a single query. For the moment I fetch the document and I query separatly each of it's related document with there ids.
all my documents are on the same collection documents_nodes and look like so:
{
"id": "document_1",
"name": "flask",
"relateds": [
"document_2",
"document_3",
"document_4"
],
"parents": [
"document1_1"
]
}
The first query is to get the initial document
db.documents_nodes.find({id: document_1})
And then I query it's relateds with a second query
db.documents_nodes.aggregate([{
$match: {
$and: [{
id: {
$in: ["document_2", "document_3", "document_2"]
}
}]
}
}])
is there a way to combine the two queries, I tried this but it doesn't work
db.documents_nodes.aggregate([
{
$match: {
uri: "https://opus.adeo.com/LMFR_prod/3206"
}
},
{
$addFields: {
newRelateds:
{
$match: {
id: {
$in: [ "$relateds" ]
}
}
}
}
}
])
"errmsg" : "Unrecognized expression '$match'",
"code" : 168,
"codeName" : "InvalidPipelineOperator"
I have found a way to do it, in case someone has the same need.
I used the $unwind to flatten the array of documents and then used the $lookup to fetch the documents by their ids and finally I group the result on a new key.
[{
$match: {
id: "document_1"
}
}, {
$unwind: {
path: '$relateds',
}
}, {
$lookup: {
from: 'documents_nodes',
localField: 'relateds',
foreignField: 'id',
as: 'newRelateds'
}
}, {
$group: {
_id: '$id',
relateds: {
'$push': '$newRelateds'
}
}
}]

How do I query a mongo document containing subset of nested array

Here is a doc I have:
var docIHave = {
_id: "someId",
things: [
{
name: "thing1",
stuff: [1,2,3,4,5,6,7,8,9]
},
{
name: "thing2",
stuff: [4,5,6,7,8,9,10,11,12,13,14]
},
{
name: "thing3",
stuff: [1,4,6,8,11,21,23,30]
}
]
}
This is the doc I want:
var docIWant = {
_id: "someId",
things: [
{
name: "thing1",
stuff: [5,6,7,8,9]
},
{
name: "thing2",
stuff: [5,6,7,8,9,10,11]
},
{
name: "thing3",
stuff: [6,8,11]
}
]
}
stuff´s of docIWant should only contain items greater than min=4
and smaller than max=12.
Background:
I have a meteor app and I subscribe to a collection giving me docIHave. Based on parameters min and max I need the docIWant "on the fly". The original document should not be modified. I need a query or procedure that returns me docIWant with the subset of stuff.
A practical code example would be greatly appreciated.
Use the aggregation framework for this. In the aggregation pipeline, consider the $match operator as your first pipeline stage. This is quite necessary to optimize your aggregation as you would need to filter documents that match the given criteria first before passing them on further down the pipeline.
Next use the $unwind operator. This deconstructs the things array field from the input documents to output a document for each element. Each output document is the input document with the value of the array field replaced by the element.
Another $unwind operation would be needed on the things.stuff array as well.
The next pipeline stage would then filter dopcuments where the deconstructed things.stuff match the given min and max criteria. Use a $match operator for this.
A $group operator is then required to group the input documents by a specified identifier expression and applies the accumulator expression $push to each group. This creates an array expression to each group.
Typically your aggregation should end up like this (although I haven't actually tested it but this should get you going in the right direction):
db.collection.aggregate([
{
"$match": {
"things.stuff": { "$gt": 4, "$lte": 11 }
}
},
{
"$unwind": "$things"
},
{
"$unwind": "$things.stuff"
},
{
"$match": {
"things.stuff": { "$gt": 4, "$lte": 11 }
}
},
{
"$group": {
"_id": {
"_id": "$_id",
"things": "$things"
},
"stuff": {
"$push": "$things.stuff"
}
}
},
{
"$group": {
"_id": "$_id._id",
"things": {
"$push": {
"name": "$_id.things.name",
"stuff": "$stuff"
}
}
}
}
])
If you need to transform the document on the client for display purposes, you could do something like this:
Template.myTemplate.helpers({
transformedDoc: function() {
// get the bounds - maybe these are stored in session vars
var min = Session.get('min');
var max = Session.get('max');
// fetch the doc somehow that needs to be transformed
var doc = SomeCollection.findOne();
// transform the thing.stuff arrays
_.each(doc.things, function(thing) {
thing.stuff = _.reject(thing.stuff, function(n) {
return (n < min) || (n > max);
});
});
// return the transformed doc
return doc;
}
});
Then in your template: {{#each transformedDoc.things}}...{{/each}}
Use mongo aggregation like following :
First use $unwind this will unwind stuff and then use $match to find elements greater than 4. After that $group data based on things.name and add required fields in $project.
The query will be as following:
db.collection.aggregate([
{
$unwind: "$things"
}, {
$unwind: "$things.stuff"
}, {
$match: {
"things.stuff": {
$gt: 4,
$lt:12
}
}
}, {
$group: {
"_id": "$things.name",
"stuff": {
$push: "$things.stuff"
}
}
}, {
$project: {
"thingName": "$_id",
"stuff": 1
}
}])

MongoDB aggregate - filter by subdocument

I have a mongodb collection with structure like that:
[
{
name: "name1",
instances: [{value:1, score:2}, {value:2, score:5}, {value:2.5, score:9}]
},
{
name: "name2",
instances: [{value:6, score:3}, {value:1, score:6}, {value:3.7, score:5.2}]
}
]
When I want to get all the data from a document, I use aggregate because I want each instance returned as a separate document:
db.myCollection.aggregate([{$match:{name:"name1"}}, {$unwind:"$instances"}, {$project:{name:1, value:"$instances.value", score:"$instances.score"}}])
And everything works like I want it to.
Now for my question: I want to filter the returned data by score or by value. For example, I want an array of all the subdocuments of name1 which have a value greater or equal to 2.
I tried to add to the $match object 'instances.value':{$gte:2}, but it didn't filter anything, and I still get all 3 documents for this query.
Any ideas?
After unwinding instances then again used $match as below
db.collectionName.aggregate({
"$match": {
"name": "name1"
}
}, {
"$unwind": "$instances"
}, {
"$match": {
"instances.value": {
"$gte": 2
}
}
}, {
$project: {
name: 1,
value: "$instances.value",
score: "$instances.score"
}
})
Or if you tried $match after project then used as below
db.collectionName.aggregate([{
$match: {
name: "name1"
}
}, {
$unwind: "$instances"
}, {
$project: {
name: 1,
value: "$instances.value",
score: "$instances.score"
}
}, {
"$match": {
"value": {
"$gte": 2
}
}
}])

FindAndModify, return array of Objects

I'm trying to modify multiple documents with findAndModify, and then return the new Documents modified.
My query is:
db.users.findAndModify({
query: {
_id: {
$in: [
ObjectId("54061f3c27afac4b44688c1d"),
ObjectId("54061f3c27afac4b44688c1e")
]
}
},
update: {
$inc: {
i: 1
}
},
new: true
});
but can retrieve only one document. My aim is to modify multi documents, and return all of them. Is it possible to retrieve an array of documents?
As explained in this page in the documents, findAndModify "modifies and returns a single document". If you want to modify multiple documents using findAndModify you will have to run it once per document. In the mongo shell, you can achieve that with some JavaScript like the following:
var oids = [ObjectId("54061f3c27afac4b44688c1d"),
ObjectId("54061f3c27afac4b44688c1e")];
docs = [];
for (var i in oids) {
id = oids[i];
doc = db.e.findAndModify({
"query": { "_id": id },
"update": { "$inc": { "i": 1 }},
"new": true
});
docs.push(doc);
}
printjson(docs);
The other option would be to run update using multi as an option and then retrieve the documents. Your code would look something like the following:
db.users.update(
{ "_id": {
"$in": [ObjectId("54061f3c27afac4b44688c1d"),
ObjectId("54061f3c27afac4b44688c1e")]
}
},
{ "$inc": { "i": 1 }},
{ "multi": true }
);
db.users.find(
{ "_id": {
"$in": [ObjectId("54061f3c27afac4b44688c1d"),
ObjectId("54061f3c27afac4b44688c1e")]
}
}
);

MongoDB concatenate strings from two fields into a third field

How do I concatenate values from two string fields and put it into a third one?
I've tried this:
db.collection.update(
{ "_id": { $exists: true } },
{ $set: { column_2: { $add: ['$column_4', '$column_3'] } } },
false, true
)
which doesn't seem to work though, and throws not ok for storage.
I've also tried this:
db.collection.update(
{ "_id": { $exists : true } },
{ $set: { column_2: { $add: ['a', 'b'] } } },
false, true
)
but even this shows the same error not ok for storage.
I want to concatenate only on the mongo server and not in my application.
You can use aggregation operators $project and $concat:
db.collection.aggregate([
{ $project: { newfield: { $concat: [ "$field1", " - ", "$field2" ] } } }
])
Unfortunately, MongoDB currently does not allow you to reference the existing value of any field when performing an update(). There is an existing Jira ticket to add this functionality: see SERVER-1765 for details.
At present, you must do an initial query in order to determine the existing values, and do the string manipulation in the client. I wish I had a better answer for you.
You could use $set like this in 4.2 which supports aggregation pipeline in update.
db.collection.update(
{"_id" :{"$exists":true}},
[{"$set":{"column_2":{"$concat":["$column_4","$column_3"]}}}]
)
Building on the answer from #rebe100x, as suggested by #Jamby ...
You can use $project, $concat and $out (or $merge) in an aggregation pipeline.
https://docs.mongodb.org/v3.0/reference/operator/aggregation/project/
https://docs.mongodb.org/manual/reference/operator/aggregation/concat/
https://docs.mongodb.com/manual/reference/operator/aggregation/out/
For example:
db.collection.aggregate(
[
{ $project: { newfield: { $concat: [ "$field1", " - ", "$field2" ] } } },
{ $out: "collection" }
]
)
With MongoDB 4.2 . . .
MongoDB 4.2 adds the $merge pipeline stage which offers selective replacement of documents within the collection, while $out would replace the entire collection. You also have the option of merging instead of replacing the target document.
db.collection.aggregate(
[
{ $project: { newfield: { $concat: [ "$field1", " - ", "$field2" ] } } },
{ $merge: { into: "collection", on: "_id", whenMatched: "merge", whenNotMatched: "discard" }
]
)
You should consider the trade-offs between performance, concurrency and consistency, when choosing between $merge and $out, since $out will atomically perform the collection replacement via a temporary collection and renaming.
https://docs.mongodb.com/manual/reference/operator/aggregation/merge/
https://docs.mongodb.com/manual/reference/operator/aggregation/merge/#merge-out-comparison
**
in my case this $concat worked for me ...
**
db.collection.update( { "_id" : {"$exists":true} },
[ {
"$set" : {
"column_2" : { "$concat" : ["$column_4","$column_3"] }
}
}
]
let suppose that you have a collection name is "myData" where you have data like this
{
"_id":"xvradt5gtg",
"first_name":"nizam",
"last_name":"khan",
"address":"H-148, Near Hero Show Room, Shahjahanpur",
}
and you want concatenate fields (first_name+ last_name +address) and save it into "address" field like this
{
"_id":"xvradt5gtg",
"first_name":"nizam",
"last_name":"khan",
"address":"nizam khan,H-148, Near Hero Show Room, Shahjahanpur",
}
now write query will be
{
var x=db.myData.find({_id:"xvradt5gtg"});
x.forEach(function(d)
{
var first_name=d.first_name;
var last_name=d.last_name;
var _add=d.address;
var fullAddress=first_name+","+last_name+","+_add;
//you can print also
print(fullAddress);
//update
db.myData.update({_id:d._id},{$set:{address:fullAddress}});
})
}
You can also follow the below.
db.collectionName.find({}).forEach(function(row) {
row.newField = row.field1 + "-" + row.field2
db.collectionName.save(row);
});
Find and Update Each Using For Loop
Try This:
db.getCollection('users').find({ }).forEach( function(user) {
user.full_name = user.first_name + " " + user.last_name;
db.getCollection('users').save(user);
});
Or Try This:
db.getCollection('users').find({ }).forEach( function(user) {
db.getCollection('users').update(
{ _id: user._id },
{ $set: { "full_name": user.first_name + " " + user.last_name } }
)
});