Convert array of objects to array of strings in mongodb - mongodb

I am looking at the following documentation.
The following inserts documents into a collection classes.
db.classes.insertMany( [
{ _id: 1, title: "Reading is ...", enrollmentlist: [ "giraffe2", "pandabear", "artie" ], days: ["M", "W", "F"] },
{ _id: 2, title: "But Writing ...", enrollmentlist: [ "giraffe1", "artie" ], days: ["T", "F"] }
] )
And the members collection:
db.members.insertMany( [
{ _id: 1, name: "artie", joined: new Date("2016-05-01"), status: "A" },
{ _id: 2, name: "giraffe", joined: new Date("2017-05-01"), status: "D" },
{ _id: 3, name: "giraffe1", joined: new Date("2017-10-01"), status: "A" },
{ _id: 4, name: "panda", joined: new Date("2018-10-11"), status: "A" },
{ _id: 5, name: "pandabear", joined: new Date("2018-12-01"), status: "A" },
{ _id: 6, name: "giraffe2", joined: new Date("2018-12-01"), status: "D" }
] )
They use the following aggregation to join the two collections on the array field, enrollmentlist.
db.classes.aggregate( [
{
$lookup:
{
from: "members",
localField: "enrollmentlist",
foreignField: "name",
as: "enrollee_info"
}
}
] )
Which returns the following:
{
"_id" : 1,
"title" : "Reading is ...",
"enrollmentlist" : [ "giraffe2", "pandabear", "artie" ],
"days" : [ "M", "W", "F" ],
"enrollee_info" : [
{ "_id" : 1, "name" : "artie", "joined" : ISODate("2016-05-01T00:00:00Z"), "status" : "A" },
{ "_id" : 5, "name" : "pandabear", "joined" : ISODate("2018-12-01T00:00:00Z"), "status" : "A" },
{ "_id" : 6, "name" : "giraffe2", "joined" : ISODate("2018-12-01T00:00:00Z"), "status" : "D" }
]
}
{
"_id" : 2,
"title" : "But Writing ...",
"enrollmentlist" : [ "giraffe1", "artie" ],
"days" : [ "T", "F" ],
"enrollee_info" : [
{ "_id" : 1, "name" : "artie", "joined" : ISODate("2016-05-01T00:00:00Z"), "status" : "A" },
{ "_id" : 3, "name" : "giraffe1", "joined" : ISODate("2017-10-01T00:00:00Z"), "status" : "A" }
]
}
How can I reduce enrolle_info just to be an array of strings with all of the names?
This is the result I am after:
{
"_id" : 1,
"title" : "Reading is ...",
"enrollmentlist" : [ "giraffe2", "pandabear", "artie" ],
"days" : [ "M", "W", "F" ],
"enrollee_info" : [
"artie",
"pandabear"
"giraffe2"
]
}
{
"_id" : 2,
"title" : "But Writing ...",
"enrollmentlist" : [ "giraffe1", "artie" ],
"days" : [ "T", "F" ],
"enrollee_info" : [
"artie",
"giraffe1"
]
}
I have also looked into using multiple joins by introducing the pipeline field inside the $lookup operation. I am able to use a $project to get an array just with {"name": "example"} but I am not sure how to remove the "name". I have tried using an {"$unwind": "$enrollee_info.name"} but that does not give me what I want. Do I need to introduce another stage to my aggregation pipeline after I do the join?

It seems that I was over complicating this. I was able to achieve my desired result by doing the following:
db.classes.aggregate( [
{
$lookup:
{
from: "members",
localField: "enrollmentlist",
foreignField: "name",
as: "enrollee_info"
}
},
{
$project:
{
"_id": 1,
"title": 1,
"days": 1,
"enrollee_names": "$enrollee_info.name"
}
}
] )
Result:
[
{
"id": 1,
"title": "Reading is ...",
"days": [
"M",
"W",
"F"
],
"names": [
"artie",
"pandabear",
"giraffe2"
]
},
{
"id": 2,
"title": "But Writing ...",
"days": [
"T",
"F"
],
"names": [
"artie",
"giraffe1"
]
}
]

Related

How to use lookup with custom condition inside subelement in MongoDB aggregation?

Helo everyone!
I have a products collection like this:
db.products.insertMany( [
{ "_id" : 1, "name" : "Apple", "variants" : [ { "_id" : 1, "name" : "Red Apple" }, { "_id" : 2, "name" : "Green Apple" }] },
{ "_id" : 2, "name" : "Banana", "variants" : [ { "_id" : 3, "name" : "Yellow Banana" }, { "_id" : 4, "name" : "Green Banana" }] },
] )
and a orders collection
db.orders.insertMany( [
{ "_id" : 1, "price" : 123, "itemId": 2},
] )
How to join products collection to orders collection by itemId (itemId == variants._id) with aggregate?
I try with this way but it's not working
db.orders.aggregate([
{
$lookup: {
from: 'products',
as: 'product',
let: { variantId: '$_id' },
pipeline: [
{
$match: {
$expr: { $eq: ['$$variantId', '$variants._id'] },
},
}
],
},
},
])
maybe issues from $expr { $eq: ['$$variantId', '$variants._id'] } but i cannot resolve it. anybody can help?
Thanks for help!

Mongo ranking results

I have a collection like
db.books.insertMany([
{"products" : [{"name": "name1", "ids": [4, 5, 6]}], "author" : "Dante", "shelf": "a" },
{ "products" : [{"name": "name1", "ids": [4, 5]}], "author" : "Homer", "shelf": "a" },
{ "products" : [{"name": "name1", "ids": [2]}], "author" : "Dante", "shelf": "b" },
])
and I want to retrieve all documents where "shelf" is 'a'
and sort by 2 conditions:
1 - by Author
2 - documents where products.ids not contains 6 should be the first.
Could anyone help?
You can try this query:
First $match the shelf value with "a".
Then create an auxiliar value where will be true if 6 not exists into products.ids, otherwise false.
Then $sort by values you want.
And use $project to remove the auxiliar value.
db.collection.aggregate([
{
"$match": {"shelf": "a"}
},
{
"$set": {
"rank": {
"$eq": [
{
"$filter": {
"input": "$products",
"cond": {"$in": [6,"$$this.ids"]}
}
},[]
]
}
}
},
{
"$sort": {
"rank": -1,
"author": 1
}
},
{
"$project": {"rank": 0}
}
])
Example here
Here is a variation that sorts more granularly on author+"not containing 6".
db.foo.aggregate([
{$match: {shelf:'a'}}
,{$unwind: '$products'}
,{$addFields: {sortMarker: {$cond: [
{$in: [6, '$products.ids']},
"Z", // THEN make sortMarker at the end
"A" // ELSE make sortMarker at the start
]}
}}
,{$sort: {'author':1, 'sortMarker':1}}
]);
which given this input set:
{"products" : [
{"name": "name3", "ids": [6, 7]},
{"name": "name2", "ids": [4, 5]}
],
"author" : "Homer",
"shelf": "a" },
{"products" : [
{"name": "name1", "ids": [4, 5, 6]},
{"name": "name4", "ids": [9]},
{"name": "name7", "ids": [9,6]},
{"name": "name7", "ids": [10]}
],
"author" : "Dante",
"shelf": "a"},
{ "products" : [
{"name": "name1", "ids": [2]}
], "author" : "Dante",
"shelf": "b"}
yields this result:
{
"_id" : 1,
"products" : {
"name" : "name4",
"ids" : [
9
]
},
"author" : "Dante",
"shelf" : "a",
"sortMarker" : "A"
}
{
"_id" : 1,
"products" : {
"name" : "name7",
"ids" : [
10
]
},
"author" : "Dante",
"shelf" : "a",
"sortMarker" : "A"
}
{
"_id" : 1,
"products" : {
"name" : "name1",
"ids" : [
4,
5,
6
]
},
"author" : "Dante",
"shelf" : "a",
"sortMarker" : "Z"
}
{
"_id" : 1,
"products" : {
"name" : "name7",
"ids" : [
9,
6
]
},
"author" : "Dante",
"shelf" : "a",
"sortMarker" : "Z"
}
{
"_id" : 0,
"products" : {
"name" : "name2",
"ids" : [
4,
5
]
},
"author" : "Homer",
"shelf" : "a",
"sortMarker" : "A"
}
{
"_id" : 0,
"products" : {
"name" : "name3",
"ids" : [
6,
7
]
},
"author" : "Homer",
"shelf" : "a",
"sortMarker" : "Z"
}
Optionally, this stage can be added after the $sort:
{$group: {_id: '$author', products: {$push: '$products'}}}
And this will bring the sorted "not containing 6 then containing 6" items together again as an array packaged by author; the $push retains the order. Note we need only need author in _id because the match was for one shelf. If more than one shelf is in the match, then we would need:
{$group: {_id: {author:'$author',shelf:'$shelf'}, products: {$push: '$products'}}}

mongodb getting an array inside a nested array

I'm still new to mongodb, I have this basic enrollment system data:
{ "_id" : ObjectId("62277d92a561e550d5ec73ca"), "sid" : 1, "sname" : "sad", "semail" : "dsa", "scourse" : "it", "enrolled" : [ { "subjid" : 3 } ] }
{ "_id" : ObjectId("6227875bdbcc41a56a863697"), "sid" : 2, "sname" : "daws", "semail" : "dws", "scourse" : "cs", "enrolled" : [ { "subjid" : 1, "grades" : [ { "prelim" : "A", "midterm" : "B", "prefinal" : "B", "final" : "A" } ] }, { "subjid" : 2, "grades" : [ { "prelim" : "D", "midterm" : "A", "prefinal" : "B", "final" : "F" } ] } ] }
I want display the grades of sid 2 who has enrolled subjid 1.
I tried using this aggregation line:
db.students2.aggregate( [{"$match":{"sid":{"$eq":2},"enrolled.subjid":{"$eq":2}}}, {$group: {_id:'$enrolled.subjid[1]', prelim:{$first:'$enrolled.grades.prelim'},midterm:{$first:'$enrolled.grades.midterm'},prefinal:{$first:'$enrolled.grades.prefinal'},"final":{$first:'$enrolled.grades.final'} } } ])
but this was the result:
{ "_id" : [ ], "prelim" : [ [ "A" ], [ "D" ] ], "midterm" : [ [ "B" ], [ "A" ] ], "prefinal" : [ [ "B" ], [ "B" ] ], "final" : [ [ "A" ], [ "F" ] ] }
I only wanted to get the grades of subjid 1 but it also got the grades of subjid 2
Maybe you need something like this:
db.collection.aggregate([
{
"$match": {
"sid": 2,
"enrolled.subjid": 1
}
},
{
"$addFields": {
"enrolled": {
"$filter": {
"input": "$enrolled",
"as": "en",
"cond": {
$eq: [
"$$en.subjid",
1
]
}
}
}
}
},
{
$unwind: "$enrolled"
},
{
$unwind: "$enrolled.grades"
},
{$limit:1}
,
{
$project: {
_id: "$enrolled.subjid",
prelim: "$enrolled.grades.prelim",
midterm: "$enrolled.grades.midterm",
prefinal: "$enrolled.grades.prefinal",
"final": "$enrolled.grades.final"
}
}
])
Explained:
Match the necessary documents (sid=2,subjid=1)
Filter only the enrolled elements based on subjid ( subjid=1 )
unwind the two array
limit to the first result document available only in case there is more.
project the necesary fields
playground

How to convert existing array of string to custom object array in mongodb?

I have some existing data and I want to update it but I can't able to build a query for that.
I want to convert and update Options field which is string array to object array.
Is it possible or not?
I tried arrayToObject but it doesn't work
Below is my existing record:
{
"_id" : ObjectId("5b6455d9c006ae9d142b0da8"),
"PartnerId" : "585938e3d4e9dac9bb2b09c6",
"BusinessID" : NumberLong(98),
"Responses" : [
{
"QID" : 1,
"Order" : 1,
"Question" : "Contact Information 1",
"Options" : [
"First Name",
"Address",
"Email",
"Phone"
],
"Answers" : [
"First Name",
"111, Dublin, California, 94568",
"forms1#vagaro.com",
"111"
]
},
{
"QID" : 8,
"Order" : 6,
"Question" : "Contact Information 2",
"Options" : [
"Address",
"Email"
],
"Answers" : [
"5000 Estate Enighed, Independence, Kansas, 67301"
]
}
]
}
Expected result:
{
"_id" : ObjectId("5b6455d9c006ae9d142b0da8"),
"PartnerId" : "585938e3d4e9dac9bb2b09c6",
"BusinessID" : NumberLong(98),
"Responses" : [
{
"QID" : 1,
"Order" : 1,
"Question" : "Contact Information 1",
"Options" : [
{Option:"First Name", Order:1},
{Option:"Address", Order:2},
{Option:"Email", Order:3},
{Option:"Phone", Order:4}
],
"Answers" : [
"First Name",
"111, Dublin, California, 94568",
"forms1#vagaro.com",
"111"
]
},
{
"QID" : 8,
"Order" : 6,
"Question" : "Contact Information 2",
"Options" : [
{Option:"Address", Order:1},
{Option:"Email" , Order:2}
],
"Answers" : [
"5000 Estate Enighed, Independence, Kansas, 67301"
]
}
]
}
Please help me.
Thanks
You can use Aggregation like follows. I used mongo shell with the same document you described.
db.test.aggregate([
{ $match: {} },
{
$project: {
_id: "$_id",
PartnerId: "$PartnerId",
BusinessID: "$BusinessID",
Responses: {
$map: {
input: "$Responses",
as: 'response',
in: {
"QID" : "$$response.QID",
"Order" : "$$response.Order",
"Question" : "$$response.Question",
"Options" : {
$map: {
input: "$$response.Options",
as: 'option',
in: {
Option: "$$option",
Order: {
$sum: [
{
$indexOfArray: [
"$$response.Options",
"$$option"
]
},
1
]
}
}
}
},
"Answers" : "$$response.Answers"
}
}
}
}
},
{ $out: 'output' }
])
Will output the desired documents to the collection output. You can check it and rename it later or just specify another collection name in the $out stage if you want to override/create another collection.

How do I create nested aggregations with count on MongoDB?

I am learning MongoDB in order to see if it matches our needs.
Currently we use heavily aggregations, so I am testing the flexibility of the Aggregation Framework.
I started with this hierarchy
db.companytest3.insert({"name":"A", age:7})
db.companytest3.insert({"name":"B", age:17, owner:"A"})
db.companytest3.insert({"name":"C", age:12, owner:"A"})
db.companytest3.insert({"name":"D", age:7, owner:"B"})
db.companytest3.insert({"name":"E", age:13, owner:"B"})
db.companytest3.insert({"name":"F", age:23, owner:"C"})
So I have:
db.companytest3.find()
{ "_id" : ObjectId("5457c2c0fa82c305e0b80006"), "name" : "A", "age" : 7 }
{ "_id" : ObjectId("5457c2cafa82c305e0b80007"), "name" : "A", "age" : 7 }
{ "_id" : ObjectId("5457c2d0fa82c305e0b80008"), "name" : "B", "age" : 17, "owner" : "A" }
{ "_id" : ObjectId("5457c2d6fa82c305e0b80009"), "name" : "C", "age" : 12, "owner" : "A" }
{ "_id" : ObjectId("5457c2ddfa82c305e0b8000a"), "name" : "D", "age" : 7, "owner" : "B" }
{ "_id" : ObjectId("5457c2e4fa82c305e0b8000b"), "name" : "E", "age" : 13, "owner" : "B" }
{ "_id" : ObjectId("5457c2eafa82c305e0b8000c"), "name" : "F", "age" : 23, "owner" : "C" }
My goal is to aggregate the children using their ages, so I have something like this:
{
"_id" : null,
"children" : [
{
"range:" : "lower than 10",
total: 1,
names: ["A"]
}
{
"range:" : "higher than 10",
total: 0,
names: []
}
],
"total" : 1
}
{
"_id" : "A",
"children" : [
{
"range:" : "lower than 10",
total: 0,
names: []
}
{
"range:" : "higher than 10",
total: 2,
names: ["C","B"]
}
],
"total" : 1
}
{
"_id" : "B",
"children" : [
{
"range:" : "lower than 10",
total: 1,
names: ["D"]
}
{
"range:" : "higher than 10",
total: 13,
names: ["E"]
}
],
"total" : 1
}
{
"_id" : "C",
"children" : [
{
"range:" : "lower than 10",
total: 0,
names: []
}
{
"range:" : "higher than 10",
total: 1,
names: ["F"]
}
],
"total" : 1
}
I feel I am getting near, I've got this query:
db.companytest3.aggregate(
{ $project: {
"_id": 0,
"range": {
$concat: [{
$cond: [ { $lte: ["$age", 10] }, "até 10", "" ]
}, {
$cond: [ { $gte: ["$age", 11] }, "mais de 10", "" ]
}]
},
"owner": "$owner",
"name" : "$name"
}
},
{
$group: {
_id: { owner: "$owner", range: "$range" },
children: { $addToSet: { name: "$name", range: "$range"} } ,
total: { $sum: 1}
}
},
{
$group: {
_id: { owner:"$_id.owner" },
children: { $addToSet: "$children" }
}
}
)
which gives me the following output:
{ "_id" : { "owner" : null }, "children" : [ [ { "name" : "A", "range" : "até 10" } ] ] }
{ "_id" : { "owner" : "A" }, "children" : [ [ { "name" : "C", "range" : "mais de 10" }, { "name" : "B", "range" : "mais de 10" } ] ] }
{ "_id" : { "owner" : "B" }, "children" : [ [ { "name" : "D", "range" : "até 10" } ], [ { "name" : "E", "range" : "mais de 10" } ] ] }
{ "_id" : { "owner" : "C" }, "children" : [ [ { "name" : "F", "range" : "mais de 10" } ] ] }
Now I am having issues to group the items by owner and keep sum the total, I am stuck and I do not know how to proceed. I've been trying many diferent alternatives using groups variations but I do not feel they are worth posting here.
How can I change my current query so I group the children by range and add the count?
thanks! :D
It should be possible in earlier versions, but even basically looking at how you want to manipulate the result, the simplest way I can see is with the help of some operators introduced in MongoDB 2.6.
db.companytest3.aggregate([
{ "$group": {
"_id": "$owner",
"lowerThanTenNames": {
"$addToSet": {
"$cond": [
{ "$lte": [ "$age", 10 ] },
"$name",
false
]
}
},
"lowerThanTenTotal": {
"$sum": {
"$cond": [
{ "$lte": [ "$age", 10 ] },
1,
0
]
}
},
"moreThanTenNames": {
"$addToSet": {
"$cond": [
{ "$gte": [ "$age", 11 ] },
"$name",
false
]
}
},
"moreThanTenTotal": {
"$sum": {
"$cond": [
{ "$gte": [ "$age", 11 ] },
1,
0
]
}
}
}},
{ "$project": {
"children": {
"$map": {
"input": { "$literal": ["L", "M"] },
"as": "el",
"in": {
"$cond": [
{ "$eq": [ "$$el", "L" ] },
{
"range": { "$literal": "lower than 10" },
"total": "$lowerThanTenTotal",
"names": {
"$setDifference": [
"$lowerThanTenNames",
[false]
]
}
},
{
"range": { "$literal": "higher than 10" },
"total": "$moreThanTenTotal",
"names": {
"$setDifference": [
"$moreThanTenNames",
[false]
]
}
}
]
}
}
},
"total": { "$add": [ "$lowerThanTenTotal", "$moreThanTenTotal" ]},
}},
{ "$sort": { "_id": 1 } }
])
Basically you want to separate these out into two sets of results for each grouping, being one for each age range. Due to the use of conditional operators, the "names" sets then need to be filtered for any false values where the conditions did not match.
The other thing that needs to be done is to coerce these results from separate fields into an array. The $map operator makes this simple by just providing a two element template with effectively "A/B" choices to do the re-mapping.
Since we had discrete fields here before they were re-mapped onto an array, you can just supply each "total" field as an argument to $add in order to get the combined total.
Produces exactly this:
{
"_id" : null,
"children" : [
{
"range" : "lower than 10",
"total" : 1,
"names" : ["A"]
},
{
"range" : "higher than 10",
"total" : 0,
"names" : [ ]
}
],
"total" : 1
}
{
"_id" : "A",
"children" : [
{
"range" : "lower than 10",
"total" : 0,
"names" : [ ]
},
{
"range" : "higher than 10",
"total" : 2,
"names" : ["C","B"]
}
],
"total" : 2
}
{
"_id" : "B",
"children" : [
{
"range" : "lower than 10",
"total" : 1,
"names" : ["D"]
},
{
"range" : "higher than 10",
"total" : 1,
"names" : ["E"]
}
],
"total" : 2
}
{
"_id" : "C",
"children" : [
{
"range" : "lower than 10",
"total" : 0,
"names" : [ ]
},
{
"range" : "higher than 10",
"total" : 1,
"names" : ["F"]
}
],
"total" : 1
}