Mongoose Aggregate with Array of Objects in $lookup - node.js

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

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

Node Mongodb Driver: different result on aggregate

how you doing?
I have a trouble making a aggregation in my project, my aggregation result is different in Robo3T and Node.
db.getCollection('companies').aggregate([
{ '$match': { _id: { '$eq': ObjectId("5e30a4fe11e6e80d7fb544a4")} } },
{ $unwind: '$jobVacancies' },
{
$project: {
jobVacancies: {
_id: 1,
name: 1,
city: 1,
openingDate: 1,
closingDate: 1,
createdAt: 1,
quantity: 1,
steps: {
$filter: {
input: '$jobVacancies.steps',
as: 'step',
cond: {
$and: [
{ $eq: ['$$step.order', 0] },
{ $ne: ['$$step.users', undefined] },
{ $ne: ['$$step.users', null] },
{ $ne: ['$$step.users', []] },
],
},
},
},
},
},
},
{ $match: { 'jobVacancies.steps': { $ne: [] } } },
])
In Robo3T this is returning 1 object, but in Node (the same aggregation) is resulting 6 objects. Can you help me? Thank you
EDIT
Nodejs:
The first match create the ObjectId match for company in context of GraphQL based on my filter.
const companies = await this.MongoClient.db
.collection('companies')
.aggregate([
{
$match: await this.getFilterObject(
filters.filter(f => !f.field.includes('$$jobVacancy') && !f.field.includes('StepOrder')),
),
},
{ $unwind: '$jobVacancies' },
{
$project: {
jobVacancies: {
_id: 1,
name: 1,
city: 1,
openingDate: 1,
closingDate: 1,
createdAt: 1,
quantity: 1,
steps: {
$filter: {
input: '$jobVacancies.steps',
as: 'step',
cond: {
$and: [
{ $eq: ['$$step.order', order] },
{ $ne: ['$$step.users', undefined] },
{ $ne: ['$$step.users', null] },
{ $ne: ['$$step.users', []] },
],
},
},
},
},
},
},
{ $match: { 'jobVacancies.steps': { $ne: [] } } },
])
.toArray();
EDIT 3
This is the result of console.dir (with {depth:null}) of the pipeline
[
{
'$match': {
_id: {
'$eq': ObjectID {
_bsontype: 'ObjectID',
id: Buffer [Uint8Array] [
94, 48, 164, 254, 17,
230, 232, 13, 127, 181,
68, 164
]
}
}
}
},
{ '$unwind': '$jobVacancies' },
{
'$project': {
jobVacancies: {
_id: 1,
name: 1,
city: 1,
openingDate: 1,
closingDate: 1,
createdAt: 1,
quantity: 1,
steps: {
'$filter': {
input: '$jobVacancies.steps',
as: 'step',
cond: {
'$and': [
{ '$eq': [ '$$step.order', 0 ] },
{ '$ne': [ '$$step.users', undefined ] },
{ '$ne': [ '$$step.users', null ] },
{ '$ne': [ '$$step.users', [] ] }
]
}
}
}
}
}
},
{ '$match': { 'jobVacancies.steps': { '$ne': [] } } }
]
I think i found the solution, the document is created with properties:
jobVacancies: {
steps: {
users: []
}
}
But sometimes users array is undefined in mongodb, so I verify with
{ '$ne': [ '$$step.users', undefined ] }
I think JS undefined is different then mongodb undefined, so I initialized all steps with an empty array of users, and removed this verification and worked! –

how to fetch seller order from multiple collection data

I have 4 collections for store order data.
1. order => field => _id, order_no, cust_id, order_date
2. order_address => field => _id, order_id, cust_name, mobile, address
3. order_details => field => _id, order_id, product_id, seller_id, qty, price
4. order_payment => field => _id, payment_type, payment_status
in that order_details collection has n number of record for a number of product in one order.
in that how to get particular seller order from my data using aggregate in node.js from MongoDB database
i try this code but it's show ordre_details = [] but show order in my result:
var query = [
{ "$lookup": {
from: 'order_details',
let: { order_id: "$_id" },
pipeline: [
{ $match: { $expr: { $and: [{ $eq: [ "$order_id", "$$order_id" ] }, { $eq: [ "$seller_id", ObjectID(seller_id) ] }] } } },
{ $project: {
amount: 1,
cod_charge: 1,
shipping_charge: 1,
pid: 1,
product_attribute_id: 1,
qty: 1
} },
{ "$lookup": {
from: 'product',
let: { product_id: "$pid", product_attribute_id: '$product_attribute_id'},
pipeline: [
{ $match: { $expr: { $eq: [ "$_id", "$$product_id" ] } } },
{ $project: { _id: 1, name: 1, sku: 1 } },
{ "$lookup": {
from: 'product_image',
let: { product_attribute_id: '$$product_attribute_id' },
pipeline: [
{ $match: { $expr: { $eq: [ "$product_attribute_id", "$$product_attribute_id" ] } } },
{ $project: { _id: 0, image: 1, is_default: 1 } },
{ $sort : { is_default: -1 } },
{ $replaceRoot: { newRoot: {_id: "$_id", image: "$image" } } }
],
as: 'product_image'
} },
{ $replaceRoot: { newRoot: {
_id: "$_id",
name: "$name",
sku: "$sku",
image: { $arrayElemAt: [ "$product_image.image", 0 ] }
} } }
],
as: 'product'
} },
{ "$replaceRoot": { newRoot: {
_id: '$$ROOT._id',
pid: '$$ROOT.pid',
amount: '$$ROOT.amount',
cod_charge: '$$ROOT.cod_charge',
shipping_charge: '$$ROOT.shipping_charge',
product_attribute_id: "$$ROOT.product_attribute_id",
qty: "$$ROOT.qty",
product: { $arrayElemAt: [ "$product", 0 ] },
} } },
],
as: 'order_details'
} },
{ "$replaceRoot": {
newRoot: {
_id: "$_id",
order_no: "$order_no",
cust_id: "$cust_id",
order_date: "$order_date",
order_details: "$order_details"
}
} }
]
orderModel.order.aggregate(query, function(err, orderData){})

MongoDB - How to retrieve multiple collections from an array of objects that have their IDs

So, I have a database with the following collections.
(Using Integers to represent the object IDs)
Books:
books = [
{ _id: 1, title: "Harry Potter" },
{ _id: 2, title: "The maze runner" }
...
]
Customers:
customers = [
{ _id: 1, name: "John" },
{ _id: 2, title: "Jane"}
...
]
Recommendations:
recommendations = [
{
customerID: 1,
recommendations: [
{ bookID: 1, likelihood: 0.9 },
{ bookID: 2, likelihood: 0.8 }
]
},
{
customerID: 2,
recommendations: [
{ bookID: 2, likelihood: 0.9 },
{ bookID: 1, likelihood: 0.8 }
]
}
...
]
Now when a request is sent to my server, containing customerID in the req.body, i want to return the recommended books for that customer, with the likelihood appended to them.
i.e :
desiredResult = [
{
_id: 1,
title: "Harry Potter",
likelihood: 0.9
},
{
_id: 2,
title: "The maze Potter",
likelihood: 0.8
}
...
]
Please, what is the MongoDB aggregation query to achieve this ?
You can use below aggregation
db.recommendations.aggregate([
{ "$match": { "customerID": 1 }},
{ "$unwind": "$recommendations" },
{ "$lookup": {
"from": "books",
"localField": "recommendations.bookID",
"foreignField": "_id",
"as": "recomndedBook"
}},
{ "$replaceRoot": {
"newRoot": {
"$mergeObjects": [
{ "$arrayElemAt": ["$recomndedBook", 0] },
"$recommendations"
]
}
}}
])
Below Aggregation may help you
db.recommendations.aggregate([
{ $match: { "customerID": 1 } },
{ $unwind: "$recommendations" },
{ $lookup: {
from: "books",
localField: "recommendations.bookID",
foreignField: "_id",
as: "recomndedBook"
}},
{
$addFields: {
title: { $arrayElemAt: [ "$recomndedBook.title", 0 ] },
likelihood: "$recommendations.likelihood",
bookID: "$recommendations.bookID"
}
},
{
$project: {
recommendations: 0,
recomndedBook: 0,
_id: 0,
}
}
])

Resources