Retrieve only queried element from a single document in mongoDB - mongodb

I have a collection like below :
`{
"topics" : [
{
"id" : "2",
"name" : "Test1",
"owner" : [
"123"
]
},
{
"id" : "3",
"name" : "Test2",
"owner" : [
"123",
"456"
]
}
]
}`
As, this data is in single document, and I want only matching elements based on their owner, I am using below query ( using filter in aggregation ), but I am getting 0 matching elements.
Query :
Thanks in advance...!!
db.getCollection('topics').aggregate([
{"$match":{"topics.owner":{"$in":["123","456"]}}},
{"$project":{
"topics":{
"$filter":{
"input":"$topics",
"as":"topic",
"cond": {"$in": ["$$topic.owner",["123","456"]]}
}},
"_id":0
}}
])
This query should produce below output :
{
"topics" : [
{
"id" : "1",
"name" : "Test1",
"owner" : ["123"]
},
{
"id" : "2",
"name" : "Test2",
"owner" : ["123","456"]
}
]
}

As the topic.owner is an array, you can't use $in directly as this compares whether the array is within in an array.
Instead, you should do as below:
$filter - Filter the document in the topics array.
1.1. $gt - Compare the result from 1.1.1 is greater than 0.
1.1.1. $size - Get the size of the array from the result 1.1.1.1.
1.1.1.1. $setIntersection - Intersect the topic.owner array with the input array.
{
"$project": {
"topics": {
"$filter": {
"input": "$topics",
"as": "topic",
"cond": {
$gt: [
{
$size: {
$setIntersection: [
"$$topic.owner",
[
"123",
"456"
]
]
}
},
0
]
}
}
},
"_id": 0
}
}
Demo # Mongo Playground

db.getCollection('topics').aggregate([
{"$unwind":"$topics"},
{"$addFields":{
"rest":{"$or":[{"$in":["12z3","$topics.owner"]},{"$in":["456","$topics.owner"]}]}
}},
{"$match":{
"rest":true
}},
{"$group":{
"_id":"$_id",
"topics":{"$push":"$topics"}
}}
])

Related

Mongo DB project elements of an array into a single array

I have a collection in my DB where each item looks like this :
{
"_id" : ObjectId("XXXXXX"),
"name" : "foo",
"products" : [{
"name" : "bar",
"code" : 123
},{
"name" : "foo",
"code" : 321
}]
}
And I want to get from a query a single array containing : [123, 321] from a specific collection.
This is what I have attempted :
db.getCollection('catalog').aggregate({
"$project": {
"products": {
"$map": {
"input": "$products",
"as" : "item",
"in" : "$$item.code"
}
}
}
})
But it fails and it stats "Last stage is undefined".
What am I doing wrong?
You can get a output like this, { "products" : [ 123, 321 ] }, using the following aggregation. Note the usage of the $reduce instead of the $map array operator.
db.collection.aggregate([
{
$project: {
_id: 0,
products: {
$reduce: {
input: "$products",
initialValue: [],
in: {
$concatArrays: [ "$$value", ["$$this.code"] ]
}
}
}
}
}
])

MongoDB - Apply similar lookup on multiple columns in main table

I'm a newbie to MongoDB, I'm trying to aggregate complete details of students in referencing with other collections.
students collection structure:
{
"_id" : ObjectId("5cc973dd008221192148177a"),
"name" : "James Paulson",
"teachers" : [
ObjectId("5cc973dd008221192148176f"),
ObjectId("5cc973dd0082211921481770")
],
"attenders": [
ObjectId("5cc973dd0082211921481732"),
ObjectId("5cc973dd008221192148173f")
]
}
staff collection structure:
{
"_id" : ObjectId("5cc973dd008221192148176f"),
"name" : "John Paul",
"subject" : [
"english",
"maths"
]
}
{
"_id" : ObjectId("5cc973dd0082211921481771"),
"name" : "Pattrick",
"subject" : [
"physics",
"history"
]
}
{
"_id" : ObjectId("5cc973dd0082211921481732"),
"name" : "Roger",
"subject" : [
"sweeper"
]
}
{
"_id" : ObjectId("5cc973dd008221192148173f"),
"name" : "Ken",
"subject" : [
"dentist"
]
}
This is the query I used for the retrieval of all teacher details of a particular student.
Query:
db.getCollection('students').aggregate([
{
$unwind: "$teachers"
},
{
$lookup:
{
from: 'staff',
localField: 'teachers',
foreignField: '_id',
as: 'teachers'
}
}
]);
Result:
{
"_id" : ObjectId("5cc973dd008221192148177a"),
"name" : "James Paulson",
"teachers" : [
{
"_id" : ObjectId("5cc973dd008221192148176f"),
"name" : "John Paul",
"subject" : [
"english",
"maths"
]
},
{
"_id" : ObjectId("5cc973dd008221192148176f"),
"name" : "Pattrick",
"subject" : [
"physics",
"history"
]
}
],
"attenders": [
ObjectId("5cc973dd0082211921481732"),
ObjectId("5cc973dd008221192148173f")
]
}
As you can see, the attenders array is also similar to teachers except the difference in column name in students table. So how to applying a similar query to the second column (attenders)? Also is there any way to select specific columns from the second table (like only _id and name from staff collection)?
Any help on this would be greatly appreciated.
You can use below aggregation with mongodb 3.6 and above
Firstly you don't need to use $unwind here as your field already contains array of ObjectIds. And to select the specific field from the referenced collection you can use the custom $lookup with pipeline and $project the fields inside it.
db.getCollection('students').aggregate([
{ "$lookup": {
"from": "staff",
"let": { "teachers": "$teachers" },
"pipeline": [
{ "$match": { "$expr": { "$in": [ "$_id", "$$teachers" ] } } }
{ "$project": { "name": 1 }}
],
"as": "teachers"
}},
{ "$lookup": {
"from": "attenders",
"let": { "attenders": "$attenders" },
"pipeline": [
{ "$match": { "$expr": { "$in": [ "$_id", "$$attenders" ] } } }
],
"as": "attenders"
}}
])

How can I merge two field arrays in many documents into a single set?

I have MongoDB collection data that looks like this:
{ "_id" : "1", "array1" : [ "1", "2" ] },
{ "_id" : "2", "array2" : [ "1", "3" ] },
{ "_id" : "3", "array1" : [ ] },
{ "_id" : "4", "array2" : [ ] },
{ "_id" : "5" },
{ "_id" : "6", "array1" : [ "3", "4" ], "array2" : [ "5" ] }
I would like to find a query that simply returns the unique array values in a single array like so:
{"_id":"theID", "result":["1", "2", "3", "4", "5"]}
The id isn't important. Note that either array1, array2, both or neither can be present in a document and that they can even be empty. I have tried many aggregations and cascading query commands and just can't come up with desired response.
To do this you will need to use the .aggregate() method which provides access to the aggregation pipeline.
The first stage in the pipeline uses the $match operator to filter out those documents where both array1 and array2 are not presents using the $exists operator and the dot notation. This operator reduce the number of documents to be processed in the down in the pipeline.
The next stage is the $project where you basically use the $setUnion to return an array containing the elements that appear in any of your array; it also filters out duplicates elements in its result. Also not the use of the $ifNull operator which returns the value of the first expression or empty array depending on whether the first expression evaluates to null (here expression are "array1" and "array2").
From there, you need to de-normalize the "arrays" field using the $unwind operator.
In the last stage of the pipeline you $group and use $addToSet accumulator operator which returns an array of unique value.
db.getCollection('collection').aggregate([
{ "$match": {
"$or": [
{ "array1.0": { "$exists": true } },
{ "array2.0": { "$exists": true } }
]
}},
{ "$project": {
"arrays": {
"$setUnion": [
{ "$ifNull": [ "$array1", [] ] },
{ "$ifNull": [ "$array2", [] ] }
]
}
}},
{ "$unwind": "$arrays" },
{ "$group": {
"_id": null,
"arrays": { "$addToSet": "$arrays" }
}}
] )
Which yields:
{ "_id" : null, "arrays" : [ "5", "3", "1", "4", "2" ] }

MongoDB aggregation on another aggreatation suggestions

I have a Json file imported into MongoDB. Every line on it is a user, and I have a field product, with the name of it. I know the value of every product, they are just few.
But this information is not stored on the Json.
I was able to do aggregation to retrieve the number of time that a user bought a product, but I would like to do a query to get directly the amount of money that each user spent.
This is my query:
db.source.aggregate([
{"$match": {
"$and":[
{"productName":{
"$in":[
"product2","product2","product3",
"product4","product5","product6"
]
}},
{ "$or": [
{"appID" : "nameOfAPP"},
{"appID": "NameOfAPP2"}
]}
]
}},
{ "$group": {
"_id": {
"id_user": "$id_user",
"productName": "$productName"
},
"count": { "$sum": 1}
}},
{ "$sort" : { "count": -1 } }
])
so the output is like that:
{ "_id" : { "id_user" : "user1", "productID" : "product2" }, "count" : 433 }
{ "_id" : { "id_user" : "user2", "productID" : "product1" }, "count" : 370 }
{ "_id" : { "id_user" : "user1", "productID" : "product3" }, "count" : 300 }
{ "_id" : { "id_user" : "user3", "productID" : "product6" }, "count" : 250 }
{ "_id" : { "id_user" : "user2", "productID" : "product5" }, "count" : 140 }
{ "_id" : { "id_user" : "user3", "productID" : "product4" }, "count" : 90 }
I know that product 1 costs 20$, product 2 costs 40$, product 3 costs 55$, product 4 costs -90$, product 5 costs 110$, product 6 costs 200$.
I would like to have an output like that:
{ "_id" : { "id_user" : "user1"}, "money_spent" : 600$ }
{ "_id" : { "id_user" : "user2"}, "money_spent" : 400$ }
etc
Can you help to get that result, I am new with MongoDB.
Thanks in advance.
If you cannot go to the original source data an are only working with an import then do this:
db.source.aggregate([
{"$match": {
"$and":[
{ "productName": {
"$in":[
"product1","product2","product3",
"product4","product5","product6"
]
}},
{ "$or": [
{"appID" : "nameOfAPP"},
{"appID": "NameOfAPP2"}
]}
]
}},
{ "$group": {
"_id": "$id_user",
"cost": {
"$sum": {
"$cond": [
{ "$eq": ["$_id.productId", "product1"] },
20,
{ "$cond": [
{ "$eq": ["$productName", "product2"] },
40,
{ "$cond": [
{ "$eq": [ "$productName", "product3"] },
55,
{ "$cond": [
{ "$eq": [ "$productName", "product4" ] },
-90,
{ "$cond": [
{ "$eq": [ "$productName", "product5" ] },
110,
200
]}
]}
]}
]}
}
}
}
}}
])
The $cond operator evaluates whether your field value matches the condition and places the appropriate value simply just $sum to get your result.
$cond provides a "ternary" operator or "if .. then .. else" that is used to evaluate the condition you provide in the first argument. You construct this to "cascade" where the condition evaluates to false in order to move on to the next condition to evaluate, otherwise return the value that matches your condition.
In this way your "known" values are applied as you aggregate for your expected total.

Mongo aggregate nested array

I have a mongo collection with following structure
{
"userId" : ObjectId("XXX"),
"itemId" : ObjectId("YYY"),
"resourceId" : 1,
"_id" : ObjectId("528455229486ca3606004ec9"),
"parameter" : [
{
"name" : "name1",
"value" : 150,
"_id" : ObjectId("528455359486ca3606004eed")
},
{
"name" : "name2",
"value" : 0,
"_id" : ObjectId("528455359486ca3606004eec")
},
{
"name" : "name3",
"value" : 2,
"_id" : ObjectId("528455359486ca3606004eeb")
}
]
}
There can be multiple documents with the same 'useId' with different 'itemId' but the parameter will have same key/value pairs in all of them.
What I am trying to accomplish is return aggregated parameters "name1", "name2" and "name3" for each unique "userId" disregard the 'itemId'. so final results would look like for each user :
{
"userId" : ObjectId("use1ID"),
"name1" : (aggregatedValue),
"name2" : (aggregatedValue),
"name3" : (aggregatedVAlue)
},
{
"userId" : ObjectId("use2ID"),
"name1" : (aggregatedValue),
"name2" : (aggregatedValue),
"name3" : (aggregatedVAlue)
}
Is it possible to accomplish this using the aggregated methods of mongoDB ? Could you please help me to build the proper query to accomplish that ?
The simplest form of this is to keep things keyed by the "parameter" "name":
db.collection.aggregate(
// Unwind the array
{ "$unwind": "$parameter"},
// Group on the "_id" and "name" and $sum "value"
{ "$group": {
"_id": {
"userId": "$userId",
"name": "$parameter.name"
},
"value": { "$sum": "$parameter.value" }
}},
// Put things into an array for "nice" processing
{ "$group": {
"_id": "$_id.userId",
"values": { "$push": {
"name": "$_id.name",
"value": "$value"
}}
}}
)
If you really need to have the "values" of names as the field values, you can do the the following. But since you are "projecting" the fields/properties then you must specify them all in your code. You cannot be "dynamic" anymore and you are coding/generating each one:
db.collection.aggregate([
// Unwind the array
{ "$unwind": "$parameter"},
// Group on the "_id" and "name" and $sum "value"
{ "$group": {
"_id": {
"userId": "$userId",
"name": "$parameter.name"
},
"value": { "$sum": "$parameter.value"}
}},
// Project out discrete "field" names with $cond
{ "$project": {
"name1": { "$cond": [
{ "$eq": [ "$_id.name", "name1" ] },
"$value",
0
]},
"name2": { "$cond": [
{ "$eq": [ "$_id.name", "name2" ] },
"$value",
0
]},
"name3": { "$cond": [
{ "$eq": [ "$_id.name", "name3" ] },
"$value",
0
]},
}},
// The $cond put "0" values in there. So clean up with $group and $sum
{ "$group": {
_id: "$_id.userId",
"name1": { "$sum": "$name1" },
"name2": { "$sum": "$name2" },
"name3": { "$sum": "$name3" }
}}
])
So while the extra steps give you the result that you want ( well with a final project to change the _id to userId ), for my mind the short version is workable enough, unless you really do need it. Consider the output from there as well:
{
"_id" : ObjectId("53245016ea402b31d77b0372"),
"values" : [
{
"name" : "name3",
"value" : 2
},
{
"name" : "name2",
"value" : 0
},
{
"name" : "name1",
"value" : 150
}
]
}
So that would be what I would use, personally. But your choice.
Not sure if I got your question but if the name field can contain only "name1", "name2", "name3" or at least you are only interested in this values, one of the possible queries could be this one:
db.aggTest.aggregate(
{$unwind:"$parameter"},
{$project: {"userId":1, "parameter.name":1,
"name1" : {"$cond": [{$eq : ["$parameter.name", "name1"]}, "$parameter.value", 0]},
"name2" : {"$cond": [{$eq : ["$parameter.name", "name2"]}, "$parameter.value", 0]},
"name3" : {"$cond": [{$eq : ["$parameter.name", "name3"]}, "$parameter.value", 0]}}},
{$group : {_id : {userId:"$userId"},
name1 : {$sum:"$name1"},
name2 : {$sum:"$name2"},
name3 : {$sum:"$name3"}}})
It firsts unwinds the parameter array, then separates name1, name2 and name3 values into different columns. There's a simple conditional statement for that. After that we can easily aggreagate by the new columns.
Hope it helps!