I have three collections Subscribers,Address,Languages
Subscribers schema has following record
{
"_id" : ObjectId("5a73fae80290f7eca89e99d4"),
"FirstName" : "Rahul",
"LastName" : "Shah",
"Address" : DBRef("Address", ObjectId("5a6ec9bda3baf2b516de5769")),
"Languages" : [
DBRef("Languages", ObjectId("5a6304ffc3c3f119fc0e60c9")),
DBRef("Languages", ObjectId("5a6ee970a3baf2b516de576b"))
]
}
Address schema has following record
{
"_id" : ObjectId("5a6ec9bda3baf2b516de5769"),
"Address1" : "Vastrapur,
"Address2" : "Satellite",
"City" : "Ahmedabad",
"Country" : "India",
"State" : "Gujarat",
"ZipCode" : "380015",
"PhoneNumber" : "(987)654-3210",
"FaxNumber" : ""
}
Languages schema has following record
{
"_id" : ObjectId("5a6304ffc3c3f119fc0e60c9"),
"Name" : "English",
"LanguageCulture" : "English",
"IsDeleted" : false
}
{
"_id" : ObjectId("5a6ee970a3baf2b516de576b"),
"Name" : "Hindi",
"LanguageCulture" : "Hindi",
"IsDeleted" : false
}
I want output be like as below
{
"_id" : ObjectId("5a73fae80290f7eca89e99d4"),
"FirstName" : "Rahul",
"LastName" : "Shah",
"Address" : {
"_id" : ObjectId("5a6ec9bda3baf2b516de5769"),
"Address1" : "Vastrapur",
"Address2" : " Satellite ",
"City" : " Ahmedabad ",
"Country" : " India ",
"State" : " Gujarat ",
"ZipCode" : " 380015 ",
"PhoneNumber" : "(987)654 - 3210 ",
"FaxNumber" : " "
},
"Languages" : [{
"_id" : ObjectId(" 5a6304ffc3c3f119fc0e60c9 "),
" Name" : " English ",
"LanguageCulture" : " English ",
"IsDeleted" : false
}, {
"_id" : ObjectId(" 5a6ee970a3baf2b516de576b "),
"Name" : " Hindi ",
"LanguageCulture" : " Hindi ",
"IsDeleted" : false
}
]
}
For that Im doing aggregation as below
db.Subscribers.aggregate([{
$project : {
"FirstName" : 1,
"LastName" : 1,
Address : {
$let : {
vars : {
refParts : {
$objectToArray : "$$ROOT.Address"
}
},
in : "$$refParts"
}
}
}
}, {
$match : {
"addressRefs" : {
$exists : true
}
}
}, {
$project : {
"FirstName" : 1,
"LastName" : 1,
"addressRefs" : {
$arrayElemAt : ["$addressRefs", 1]
}
}
}, {
$lookup : {
from : "Addresses",
localField : "addressRefs",
foreignField : "_id",
as : "address_data"
}
}, {
$project : {
"FirstName" : 1,
"LastName" : 1,
"AddressUuid" : {
$arrayElemAt : ["$address_data.uuid", 0]
}
}
}
])
Here you go. It's not pretty because of your use of DBRefs but it gets the job done thanks to the following SO answer:
Mongo how to $lookup with DBRef
db.Subscribers.aggregate([{
$addFields: {
"Address": {$arrayElemAt: [{$objectToArray: "$Address"}, 1]}, // simple DBRef transformation if the field is not an array of DBRefs
"Languages": {
$map: {
input: {
$map: {
input: "$Languages",
in: {
$arrayElemAt: [{$objectToArray: "$$this"}, 1]
},
}
},
in: "$$this.v"
}
}
}
}, {
$lookup: {
from: "Address",
localField: "Address.v", // note the .v here
foreignField: "_id",
as: "Address"
}
}, {
$lookup: {
from: "Languages",
localField: "Languages",
foreignField: "_id",
as: "Languages"
}
}, {
$addFields: {
"Address": { $arrayElemAt: [ "$Address", 0 ] } // you can replace this step with an $unwind to 'flatten' the single-value array but you might want to use preserveNullAndEmptyArrays: true in order to cater for subscribers that come without valid Address references
}
}])
Related
For MongoDB, when using $lookup to query more than one collection, is it possible to get a values-only list for a field returned in the $lookup?
What I don't want is a list of the full object with all its key/values.
Data:
failover_tool:PRIMARY> db.foo.find().pretty()
{
"_id" : ObjectId("5ce72e415267960532b8df09"),
"name" : "foo1",
"desc" : "first foo"
}
{
"_id" : ObjectId("5ce72e4a5267960532b8df0a"),
"name" : "foo2",
"desc" : "second foo"
}
failover_tool:PRIMARY> db.bar.find().pretty()
{
"_id" : ObjectId("5ce72e0c5267960532b8df06"),
"name" : "bar1",
"foo" : "foo1"
}
{
"_id" : ObjectId("5ce72e165267960532b8df07"),
"name" : "bar2",
"foo" : "foo1"
}
{
"_id" : ObjectId("5ce72e1d5267960532b8df08"),
"name" : "bar3",
"foo" : "foo2"
}
Desired Query Output
{
"_id" : ObjectId("5ce72e415267960532b8df09"),
"name" : "foo1",
"desc" : "first foo",
"bars" : ["bar1", "bar2"]
},
{
"_id" : ObjectId("5ce72e4a5267960532b8df0a"),
"name" : "foo2",
"desc" : "second foo",
"bars" : ["bar3"]
}
Closest
This query seems like it's almost there, but it returns too much data in the bars field:
db.foo.aggregate({
$lookup: {
from:"bar",
localField:"name",
foreignField: "foo",
as:"bars"
}
}).pretty()
Just use .dot notation with the name field
db.foo.aggregate([
{ "$lookup": {
"from": "bar",
"localField": "name",
"foreignField": "foo",
"as": "bars"
}},
{ "$addFields": { "bars": "$bars.name" }}
])
MongoPlayground
Hope below query helps :
db.foo.aggregate([{
$lookup: {
from:"bar",
localField:"name",
foreignField: "foo",
as:"bars"
}
},
{$unwind : '$bars'},
{
$group : {
_id : {
_id : '$_id',
name : '$name',
desc : '$desc'
},
bars : { $push : '$bars.name'}
}
},
{
$project : {
_id : '$_id._id',
name : '$_id.name',
desc : '$_id.desc',
bars : '$bars'
}
}
]).pretty()
output :
{
"_id" : ObjectId("5ce72e4a5267960532b8df0a"),
"name" : "foo2",
"desc" : "second foo",
"bars" : [
"bar3"
]
}
{
"_id" : ObjectId("5ce72e415267960532b8df09"),
"name" : "foo1",
"desc" : "first foo",
"bars" : [
"bar1",
"bar2"
]
}
I am having problems aggregating my Product Document in MongoDB.
My Product Document is:
{
"_id" : ObjectId("5d81171c2c69f45ef459e0af"),
"type" : "T-Shirt",
"name" : "Panda",
"description" : "Panda's are cool.",
"image" : ObjectId("5d81171c2c69f45ef459e0ad"),
"created_at" : ISODate("2019-09-17T18:25:48.026+01:00"),
"is_featured" : false,
"sizes" : [
"XS",
"S",
"M",
"L",
"XL"
],
"tags" : [ ],
"pricing" : {
"price" : 26,
"sale_price" : 8
},
"categories" : [
ObjectId("5d81171b2c69f45ef459e086"),
ObjectId("5d81171b2c69f45ef459e087")
],
"sku" : "5d81171c2c69f45ef459e0af"
},
And my Category Document is:
{
"_id" : ObjectId("5d81171b2c69f45ef459e087"),
"name" : "Art",
"description" : "These items are our artsy options.",
"created_at" : ISODate("2019-09-17T18:25:47.196+01:00")
},
My aim is to perform aggregation on the Product Document in order to count the number of items within each Category. So I have the Category "Art", I need to count the products are in the "Art" Category:
My current aggregate:
db.product.aggregate(
{ $unwind : "$categories" },
{
$group : {
"_id" : { "name" : "$name" },
"doc" : { $push : { "category" : "$categories" } },
}
},
{ $unwind : "$doc" },
{
$project : {
"_id" : 0,
"name" : "$name",
"category" : "$doc.category"
}
},
{
$group : {
"_id" : "$category",
"name": { "$first": "$name" },
"items_in_cat" : { $sum : 1 }
}
},
{ "$sort" : { "items_in_cat" : -1 } },
)
Which does actually work but not as I need:
{
"_id" : ObjectId("5d81171b2c69f45ef459e082"),
"name" : null, // Why is the name of the category no here?
"items_in_cat" : 4
},
As we can see the name is null. How can I aggregate the output to be:
{
"_id" : ObjectId("5d81171b2c69f45ef459e082"),
"name" : "Art",
"items_in_cat" : 4
},
We need to use $lookup to fetch the name from Category collection.
The following query can get us the expected output:
db.product.aggregate([
{
$unwind:"$categories"
},
{
$group:{
"_id":"$categories",
"items_in_cat":{
$sum:1
}
}
},
{
$lookup:{
"from":"category",
"let":{
"id":"$_id"
},
"pipeline":[
{
$match:{
$expr:{
$eq:["$_id","$$id"]
}
}
},
{
$project:{
"_id":0,
"name":1
}
}
],
"as":"categoryLookup"
}
},
{
$unwind:{
"path":"$categoryLookup",
"preserveNullAndEmptyArrays":true
}
},
{
$project:{
"_id":1,
"name":{
$ifNull:["$categoryLookup.name","NA"]
},
"items_in_cat":1
}
}
]).pretty()
Data set:
Collection: product
{
"_id" : ObjectId("5d81171c2c69f45ef459e0af"),
"type" : "T-Shirt",
"name" : "Panda",
"description" : "Panda's are cool.",
"image" : ObjectId("5d81171c2c69f45ef459e0ad"),
"created_at" : ISODate("2019-09-17T17:25:48.026Z"),
"is_featured" : false,
"sizes" : [
"XS",
"S",
"M",
"L",
"XL"
],
"tags" : [ ],
"pricing" : {
"price" : 26,
"sale_price" : 8
},
"categories" : [
ObjectId("5d81171b2c69f45ef459e086"),
ObjectId("5d81171b2c69f45ef459e087")
],
"sku" : "5d81171c2c69f45ef459e0af"
}
Collection: category
{
"_id" : ObjectId("5d81171b2c69f45ef459e086"),
"name" : "Art",
"description" : "These items are our artsy options.",
"created_at" : ISODate("2019-09-17T17:25:47.196Z")
}
{
"_id" : ObjectId("5d81171b2c69f45ef459e087"),
"name" : "Craft",
"description" : "These items are our artsy options.",
"created_at" : ISODate("2019-09-17T17:25:47.196Z")
}
Output:
{
"_id" : ObjectId("5d81171b2c69f45ef459e087"),
"items_in_cat" : 1,
"name" : "Craft"
}
{
"_id" : ObjectId("5d81171b2c69f45ef459e086"),
"items_in_cat" : 1,
"name" : "Art"
}
For MongoDB, when using $lookup to query more than one collection, is it possible to get a values-only list for a field returned in the $lookup?
What I don't want is a list of the full object with all its key/values.
Data:
failover_tool:PRIMARY> db.foo.find().pretty()
{
"_id" : ObjectId("5ce72e415267960532b8df09"),
"name" : "foo1",
"desc" : "first foo"
}
{
"_id" : ObjectId("5ce72e4a5267960532b8df0a"),
"name" : "foo2",
"desc" : "second foo"
}
failover_tool:PRIMARY> db.bar.find().pretty()
{
"_id" : ObjectId("5ce72e0c5267960532b8df06"),
"name" : "bar1",
"foo" : "foo1"
}
{
"_id" : ObjectId("5ce72e165267960532b8df07"),
"name" : "bar2",
"foo" : "foo1"
}
{
"_id" : ObjectId("5ce72e1d5267960532b8df08"),
"name" : "bar3",
"foo" : "foo2"
}
Desired Query Output
{
"_id" : ObjectId("5ce72e415267960532b8df09"),
"name" : "foo1",
"desc" : "first foo",
"bars" : ["bar1", "bar2"]
},
{
"_id" : ObjectId("5ce72e4a5267960532b8df0a"),
"name" : "foo2",
"desc" : "second foo",
"bars" : ["bar3"]
}
Closest
This query seems like it's almost there, but it returns too much data in the bars field:
db.foo.aggregate({
$lookup: {
from:"bar",
localField:"name",
foreignField: "foo",
as:"bars"
}
}).pretty()
Just use .dot notation with the name field
db.foo.aggregate([
{ "$lookup": {
"from": "bar",
"localField": "name",
"foreignField": "foo",
"as": "bars"
}},
{ "$addFields": { "bars": "$bars.name" }}
])
MongoPlayground
Hope below query helps :
db.foo.aggregate([{
$lookup: {
from:"bar",
localField:"name",
foreignField: "foo",
as:"bars"
}
},
{$unwind : '$bars'},
{
$group : {
_id : {
_id : '$_id',
name : '$name',
desc : '$desc'
},
bars : { $push : '$bars.name'}
}
},
{
$project : {
_id : '$_id._id',
name : '$_id.name',
desc : '$_id.desc',
bars : '$bars'
}
}
]).pretty()
output :
{
"_id" : ObjectId("5ce72e4a5267960532b8df0a"),
"name" : "foo2",
"desc" : "second foo",
"bars" : [
"bar3"
]
}
{
"_id" : ObjectId("5ce72e415267960532b8df09"),
"name" : "foo1",
"desc" : "first foo",
"bars" : [
"bar1",
"bar2"
]
}
For MongoDB, when using $lookup to query more than one collection, is it possible to get a values-only list for a field returned in the $lookup?
What I don't want is a list of the full object with all its key/values.
Data:
failover_tool:PRIMARY> db.foo.find().pretty()
{
"_id" : ObjectId("5ce72e415267960532b8df09"),
"name" : "foo1",
"desc" : "first foo"
}
{
"_id" : ObjectId("5ce72e4a5267960532b8df0a"),
"name" : "foo2",
"desc" : "second foo"
}
failover_tool:PRIMARY> db.bar.find().pretty()
{
"_id" : ObjectId("5ce72e0c5267960532b8df06"),
"name" : "bar1",
"foo" : "foo1"
}
{
"_id" : ObjectId("5ce72e165267960532b8df07"),
"name" : "bar2",
"foo" : "foo1"
}
{
"_id" : ObjectId("5ce72e1d5267960532b8df08"),
"name" : "bar3",
"foo" : "foo2"
}
Desired Query Output
{
"_id" : ObjectId("5ce72e415267960532b8df09"),
"name" : "foo1",
"desc" : "first foo",
"bars" : ["bar1", "bar2"]
},
{
"_id" : ObjectId("5ce72e4a5267960532b8df0a"),
"name" : "foo2",
"desc" : "second foo",
"bars" : ["bar3"]
}
Closest
This query seems like it's almost there, but it returns too much data in the bars field:
db.foo.aggregate({
$lookup: {
from:"bar",
localField:"name",
foreignField: "foo",
as:"bars"
}
}).pretty()
Just use .dot notation with the name field
db.foo.aggregate([
{ "$lookup": {
"from": "bar",
"localField": "name",
"foreignField": "foo",
"as": "bars"
}},
{ "$addFields": { "bars": "$bars.name" }}
])
MongoPlayground
Hope below query helps :
db.foo.aggregate([{
$lookup: {
from:"bar",
localField:"name",
foreignField: "foo",
as:"bars"
}
},
{$unwind : '$bars'},
{
$group : {
_id : {
_id : '$_id',
name : '$name',
desc : '$desc'
},
bars : { $push : '$bars.name'}
}
},
{
$project : {
_id : '$_id._id',
name : '$_id.name',
desc : '$_id.desc',
bars : '$bars'
}
}
]).pretty()
output :
{
"_id" : ObjectId("5ce72e4a5267960532b8df0a"),
"name" : "foo2",
"desc" : "second foo",
"bars" : [
"bar3"
]
}
{
"_id" : ObjectId("5ce72e415267960532b8df09"),
"name" : "foo1",
"desc" : "first foo",
"bars" : [
"bar1",
"bar2"
]
}
I have following collections:
Collection A
{
"_id" : ObjectId("5aaa3b170e26ed1eba223ba9"),
"name" : "A1",
"ref" : {
"$ref" : "B",
"$id" : ObjectId("5aaa33740e26ed1eba223ba1")
}
}
{
"_id" : ObjectId("5aaa3b170e26ed1eba223baa"),
"name" : "A2",
"ref" : {
"$ref" : "C",
"$id" : ObjectId("5aaa33740e26ed1eba223ba2")
}
}
Collection B
{
"_id" : ObjectId("5aaa33740e26ed1eba223ba1"),
"name" : "B1"
}
...
Collection C
{
"_id" : ObjectId("5aaa33740e26ed1eba223ba2"),
"name" : "C1"
}
...
It is posable to get folowing result?
{
"_id" : ObjectId("5aaa3b170e26ed1eba223ba9"),
"name" : "A1",
"result" : [
{
"name" : "B1"
}
]
}
{
"_id" : ObjectId("5aaa3b170e26ed1eba223baa"),
"name" : "A2",
"result" : [
{
"name" : "C1"
}
]
}
I tried it with $Project and §Lookups, unfortunately withou success.
Hire is example:
db.A.aggregate([
{$project: {
name : 1,
refId: {$arrayElemAt: [{$objectToArray:"$ref"},1]},
refCol: {$arrayElemAt: [{$objectToArray:"$ref"},0]},
}
},
{$lookup : {
from : "refCol.v",
localField : "refId.v",
foreignField : "_id",
as : "result"
}
},
{$project : {"result._id" : 0, refId : 0, refCol : 0}}
])
In this example I can't reference the "refCol.v" field in the $lookup function.
Have someone a tip or a better solution for me?
You can achieve this by populate() method:
const collectionA = require('../models/collectionA');
collectionA.find({}).populate({
path: 'collectionB',
select: {
'name': 1,
'_id': 0
}
})
}).then((data) => {
if (data) {
res.send(data);
}
}).catch((err) => {
res.send(err);
})