Mongoose aggregate merge data in one collection after lookup to one object - node.js

I have a collection A with data like
{
id: 2,
name: "test"
},
{
id: 4,
name: "test4"
}
and I have a second collection B with data like:
{
id: 444,
name: "a",
colA_id: 2
},
{
id: 555,
name: "b",
colA_id: 2
},
{
id: 555,
name: "c",
colA_id: 10
},
After I aggregate both collections, I want an output like:
{
id: 2,
name: "test",
list_of_b: {
{
id: 444,
name: "a",
colA_id: 2
},
{
id: 555,
name: "b",
colA_id: 2
},
}
}
I tried to merge them, but every time I only get one record of the database.

Its just a simple look up
The DB is
db={
"collA": [
{
id: 2,
name: "test"
},
{
id: 4,
name: "test4"
}
],
"collB": [
{
id: 444,
name: "a",
colA_id: 2
},
{
id: 555,
name: "b",
colA_id: 2
},
{
id: 555,
name: "c",
colA_id: 10
}
]
}
And the aggregation is
db.collA.aggregate([
{
"$lookup": {
"from": "collB",
"localField": "id",
"foreignField": "colA_id",
"as": "list_of_b"
}
}
])
Working Mongo playground

Related

how select all existing fields in a collection from an field of type object in mongodb

I have a collection that have a field named "data" that can have any fields, and I have to get all existing fields in all collections in that "data" field or get the documents that have diferents fields in that "data" field.
for example, if I have:
[
{
_id: "45454",
name: "fulano",
city: "cali",
data: {
age: 12,
lastName: "panguano",
cars: 0
}
},
{
_id: "67899",
name: "juanito",
city: "cali",
data: {
age: 23,
lastName: "merlano",
cars: 2
}
},
{
_id: "67899",
name: "olito",
city: "nw",
data: {
lastName: "betito",
cars: 2
}
},
{
_id: "11223",
name: "cabrito",
city: "trujillo",
data: {
age: 28,
cars: 1,
moto: 3
}
},
]
what I would like to get:
["age", "lastName", "cars", "moto"]
or :
documents where the "data" fields vary, regardless of their values.
[
{
_id: "45454",
name: "fulano",
city: "cali",
data: {
age: 12,
lastName: "panguano",
cars: 0
}
},
{
_id: "67899",
name: "olito",
city: "nw",
data: {
lastName: "betito",
cars: 2
}
},
{
_id: "11223",
name: "cabrito",
city: "trujillo",
data: {
age: 28,
cars: 1,
moto: 3
}
}
]
THE COLLECTION HAVE SO MANY DOCUMENTS CAN BE A PROBLEM IF I USE
FINDALL AND THEN USE A LOOP LIKE FOR (FOR THE RESOURCES)
Regardless how you execute this (in memory or on the db) this is a very expensive query, with that said I agree doing this in memory is the wrong approach.
Here's how to do it using the aggregation pipeline and some standard operators like $map and $objectToArray:
db.collection.aggregate([
{
$project: {
keys: {
$map: {
input: {
"$objectToArray": "$data"
},
in: "$$this.k"
}
}
}
},
{
"$unwind": "$keys"
},
{
$group: {
_id: "$keys"
}
}
])
Mongo Playground
Here's a way using javascript once you have an array of all documents in the collection:
let arr = [
{
_id: "45454",
name: "fulano",
city: "cali",
data: {
age: 12,
lastName: "panguano",
cars: 0
}
},
{
_id: "67899",
name: "juanito",
city: "cali",
data: {
age: 23,
lastName: "merlano",
cars: 2
}
},
{
_id: "67899",
name: "olito",
city: "nw",
data: {
lastName: "betito",
cars: 2
}
},
{
_id: "11223",
name: "cabrito",
city: "trujillo",
data: {
age: 28,
cars: 1,
moto: 3
}
},
]
You can use the .map method to get an array of the data objects like so:
arr = arr.map(obj => obj.data)
This will return
[
{
"age": 12,
"lastName": "panguano",
"cars": 0
},
{
"age": 23,
"lastName": "merlano",
"cars": 2
},
{
"lastName": "betito",
"cars": 2
},
{
"age": 28,
"cars": 1,
"moto": 3
}
]
Then you can get an array of data object keys by looping through the array of data objects like so:
let dataKeys = [];
arr.forEach(obj => {
dataKeys = [...dataKeys, ...Object.keys(obj)]
})
This returns an array of non unique keys:
dataKeys = [
"age",
"lastName",
"cars",
"age",
"lastName",
"cars",
"lastName",
"cars",
"age",
"cars",
"moto"
]
Then filter out the unique keys using .filter and .findIndex methods:
let uniqueKeys = dataKeys.filter((elem, index) => dataKeys.findIndex(obj => obj === elem) === index)
And this will give you
[
"age",
"lastName",
"cars",
"moto"
]

Mongodb search field with range inside array of object

I have multiple documents in a collection like this
[
{
_id: 123,
data: 1,
details: [
{
item: "a",
day: 1
},
{
item: "a",
day: 2
},
{
item: "a",
day: 3
},
{
item: "a",
day: 4
}
],
someMoreField: "xyz"
}
]
Now I want document with _id: 123 and details field should only contain day within range of 1 to 3. So the result will be like below.
{
_id: 123,
data: 1,
details: [
{
item: 'a',
day: 1,
},
{
item: 'a',
day: 2,
},
{
item: 'a',
day: 3,
},
],
someMoreField: 'xyz',
};
I tried to do this by aggregate query as:
db.collectionaggregate([
{
$match: {
_id: id,
'details.day': { $gt: 1, $lte: 3 },
},
},
{
$project: {
_id: 1,
details: {
$filter: {
input: '$details',
as: 'value',
cond: {
$and: [
{ $gt: ['$$value.date', 1] },
{ $lt: ['$$value.date', 3] },
],
},
},
},
},
},
])
But this gives me empty result. Could someone please guide me through this?
You are very close, you just need to change the $gt to $gte and $lt to $lte.
Another minor syntax error is you're accessing $$value.date but the schema you provided does not have that field, it seems you need to change it to $$value.day, like so:
db.collection.aggregate([
{
$match: {
_id: 123,
"details.day": {
$gt: 1,
$lte: 3
}
}
},
{
$project: {
_id: 1,
details: {
$filter: {
input: "$details",
as: "value",
cond: {
$and: [
{
$gte: [
"$$value.day",
1
]
},
{
$lte: [
"$$value.day",
3
]
},
],
},
},
},
},
},
])
Mongo Playground

Mongoose Aggregate with Array of Objects in $lookup

I have following Collections:
Collection A:
{
_id: 123,
name: "A",
amount: 2,
some_other_information: "ttt"
}
{
_id: 223,
name: "B",
amount: 2,
some_other_information: "ggg"
}
Collection B:
{
_id: 123,
name: "K",
amount: 2,
some_other_information: "fff"
}
{
_id: 2,
name: "L",
amount: 2,
some_other_information: "vvv"
}
Collection: D
{
_id: 123,
name: "test",
items: [
{
_id: 1,
a_id: 123,
b_id: 1,
c_id: 123
},
{
_id: 2,
a_id: 223,
b_id: 2,
c_id: 223
},
{
_id: 3,
a_id: 345,
b_id: 3
},
]
}
I want to aggregate Collection D with Collection A, with the a_id in the D collections in the items Array.
So that I have following output:
{
_id: 123,
name: "test",
items: [
{
_id: 1,
a: {
_id: 123,
name: "A",
amount: 2,
some_other_information: "ttt"
},
b: {
_id: 123,
name: "K",
amount: 2,
some_other_information: "fff"
},
c: {
_id: 123,
name: "A",
amount: 2,
some_other_information: "ttt"
}
},
{
_id: 2,
a: {
_id: 223,
name: "B",
amount: 2,
some_other_information: "ggg"
},
b: {
_id: 2,
name: "L",
amount: 2,
some_other_information: "vvv"
},
c: {
_id: 223,
name: "B",
amount: 2,
some_other_information: "ggg"
}
}
]
}
I tried
const x = await D.aggregate([
{
$lookup: {
from: "A",
localField: "items.a_id",
foreignField: "_id",
as: "a_items",
},
},
{
$project: {
_id: 1,
name: 1,
items: {
$map: {
input: "$a_items",
as: "ri",
in: {
$mergeObjects: [
"$$ri",
{
$arrayElemAt: [
{
$filter: {
input: "$items",
cond: {
$eq: [
"$$this._id",
"$$ri.a_id"
]
}
}
},
0
]
}
],
}
}
}
}
}]);
But in this way it is not aggregated with only the a_id. It shows every item in A Collection. Not to mention that it doesn't include c_id (which is the same as a_id, but it is not required, so it can be null).
So I don't know what to try anymore. Would be very helpful if someone can help.
Thanks in advance!
$lookup with collection A
$lookup with collection B
You can add more lookup if you want for collection C
$map to iterate loop of items array
show required fields in in
a add a field and filter items from a_items and get matching element and get first element
b add a field and filter items from b_items and get matching element and get first element
same as you can add c if you want
db.colD.aggregate([
{
$lookup: {
from: "colA",
localField: "items.a_id",
foreignField: "_id",
as: "a_items"
}
},
{
$lookup: {
from: "colB",
localField: "items.b_id",
foreignField: "_id",
as: "b_items"
}
},
{
$project: {
_id: 1,
name: 1,
items: {
$map: {
input: "$items",
as: "i",
in: {
_id: "$$i._id",
a: {
$arrayElemAt: [
{
$filter: {
input: "$a_items",
cond: { $eq: ["$$this._id", "$$i.a_id"] }
}
},
0
]
},
b: {
$arrayElemAt: [
{
$filter: {
input: "$b_items",
cond: { $eq: ["$$this._id", "$$i.b_id"] }
}
},
0
]
}
}
}
}
}
}
])
Playground

How to find Mongoose data recursively?

I am newbie in MEANJS and i have a problem i.e, there are collection called employee and have multiple documents with their boss field. Now i want get all employees with their lower level.
For example:-
1) {_id:ObjectId('587dcd3edca5f235f862fdfd'), name:'John'} //he doesn't have boss
2) {_id:ObjectId('587dcd3edca5f235f86dddew'), name: 'Jimmy', 'boss': ObjectId('587dcd3edca5f235f862fdfd')} //john is boss
3) {_id:ObjectId('587dcd3edca5f235f863ew'), name: 'David', 'boss': ObjectId('587dcd3edca5f235f86dddew')} //john,Jimmy are bosses
4) {_id:ObjectId('587dcd3edca5f235f86qwa'), name: 'Dyan', 'boss': ObjectId('587dcd3edca5f235f86dddew')} //john,Jimmy,David are bosses
5) {_id:ObjectId('587dcd3edca5f235f8ew32'), name:'Jack', 'boss': ObjectId('587dcd3edca5f235f862fdfd')} //john is boss
6) {_id:ObjectId('587dcd3edca5f2wsw23rlot'), name: 'Loren', 'boss':ObjectId('587dcd3edca5f235f8ew32')} //john,Jack is boss
If we take
Jonh then output will ['Jimmy','Jack','David','Dyan','Loren']
Jack then output will ['Loren']
Here is my try code:-
getBosses(user._id)
function getBosses(id){
User.find({boss:id})
.exec(function(err,users){
if(err)
return console.log(err);
//How handle here 'users' array
//for something getBosses call recursively
})
}
As far as I understood you need to find all subordinates of that people. I think the best way to do it is using $graphLookup.
db.bosses.insertMany([
{ _id: "587dcd3edca5f235f862fdfd", name: "John" },
{
_id: "587dcd3edca5f235f86dddew",
name: "Jimmy",
boss: "587dcd3edca5f235f862fdfd",
},
{
_id: "587dcd3edca5f235f863ew",
name: "David",
boss: "587dcd3edca5f235f86dddew",
},
{
_id: "587dcd3edca5f235f86qwa",
name: "Dyan",
boss: "587dcd3edca5f235f86dddew",
},
{
_id: "587dcd3edca5f235f8ew32",
name: "Jack",
boss: "587dcd3edca5f235f862fdfd",
},
{
_id: "587dcd3edca5f2wsw23rlot",
name: "Loren",
boss: "587dcd3edca5f235f8ew32",
},
]);
db.bosses.aggregate([
{
$graphLookup: {
from: "bosses",
startWith: "$_id",
connectFromField: "_id",
connectToField: "boss",
as: "subordinates",
},
},
{
$project: {
_id: false,
name: true,
subordinates: {
$reduce: {
input: "$subordinates",
initialValue: "",
in: { $concat: ["$$value", ", ", "$$this.name"] },
},
},
},
},
{
$project: {
name: true,
subordinates: { $substrBytes: ["$subordinates", 2, -1] },
},
},
]);
The result of the last one is:
[
{ name: 'John', subordinates: 'David, Dyan, Loren, Jack, Jimmy' },
{ name: 'Jimmy', subordinates: 'David, Dyan' },
{ name: 'David', subordinates: '' },
{ name: 'Dyan', subordinates: '' },
{ name: 'Jack', subordinates: 'Loren' },
{ name: 'Loren', subordinates: '' }
]
The most important thing is $graphLookup stage of the aggregate pipeline. Last two $project stages is just response formatting - return only name and subordinates as string field with comma separated names.
To get data for a specific person you can use $match stage before $graphLookup like that:
db.bosses.aggregate([
{ $match: { name: "John" } },
{
$graphLookup: ...

MongoDB (Mongoose) how to returning all document fields using $elemMatch

According with MongoDB documentation (http://docs.mongodb.org/manual/reference/operator/projection/elemMatch/):
{
_id: 1,
school: "school A",
zipcode: 63109,
students: [
{ name: "john", school: 102, age: 10 },
{ name: "jess", school: 102, age: 11 },
{ name: "jeff", school: 108, age: 15 }
]
}
{
_id: 2,
school: "school B",
zipcode: 63110,
students: [
{ name: "ajax", school: 100, age: 7 },
{ name: "achilles", school: 100, age: 8 },
]
}
{
_id: 3,
school: "school C",
zipcode: 63109,
students: [
{ name: "ajax", school: 100, age: 7 },
{ name: "achilles", school: 100, age: 8 },
]
}
{
_id: 4,
school: "school D",
zipcode: 63109,
students: [
{ name: "barney", school: 102, age: 7 },
]
}
launching:
schools.find({ zipcode: 63109}, {students: { $elemMatch: { school: 102
} } }, function (err, school) { ...}
The operation returns the following documents:
{ "_id" : 1, "students" : [ { "name" : "john", "school" : 102, "age" :
10 } ] } { "_id" : 3 } { "_id" : 4, "students" : [ { "name" :
"barney", "school" : 102, "age" : 7 } ] }
But I need the value of school filed too...
{ "_id" : 1, "school": "School A", "students" : [ { "name" : "john", "school" : 102, "age" :
10 } ] } { "_id" : 3, "school": "School C" } { "_id" : 4, "school": "School D", "students" : [ { "name" :
"barney", "school" : 102, "age" : 7 } ] }
and I can't find a way to achieve this...
http://docs.mongodb.org/manual/reference/method/db.collection.find/
If the projection argument is specified, the matching documents
contain only the projection fields and the _id field. You can
optionally exclude the _id field.
but... I forced the Fields to Return using:
schools.find({ zipcode: 63109}, {school: 1, students: { $elemMatch: { school: 102 } } }, function (err, school) { ...}
and all seems to work properly...
According to mongodb [documentation][1] $elemMatch return first matching element from an array based on a condition. So you have to use $filter instead of $elemMatch to get all matching element.
I have written a solution. please take a look.
solution checkup link: https://mongoplayground.net/p/cu7Mf8XZHDI
db.collection.find({},
{
students: {
$filter: {
input: "$students",
as: "student",
cond: {
$or: [
{
$eq: [
"$$student.age",
8
]
},
{
$eq: [
"$$student.age",
15
]
}
]
}
}
}
})
[1]: http://docs.mongodb.org/manual/reference/operator/projection/elemMatch/

Resources