Mapping a field to another ObjectId of different collection in MongoDB - javascript

I am just wondering if its possible to use a $lookup aggregation operator inside of the in field of a $map operator. I am trying to map the VINs inside the carData objects to corresponding document ids in a VIN collection. Is there a way to accomplish this using $lookup inside of $map where I can match the vin field inside my carData objects with vin fields inside of my VIN collection to map to an id as opposed to the vin.
CarData Collection:
carData: [
{vin: 123456789,
make: "Dodge"},
{vin: 987654321,
make: "Honda"}
]
Vin Collection:
[
{
_id: ObjectId("1dsf1234125")
Vin: 123456789
},
{
_id: ObjectId("1dsf1234124")
Vin: 987654321
},
]
Expected result
carData: [
{vin: ObjectId("1dsf1234125"),
make: "Dodge"},
{vin: ObjectId("1dsf1234124"),
make: "Honda"}
]

Using the data samples from the question post, this aggregation returns the expected result (change the collection names appropriately):
db.cars_collection.aggregate([
{
$lookup:
{
from: "vins_collection",
localField: "vin",
foreignField: "Vin",
as: "car_vins"
}
},
{
$project: {
_id: 0,
make: 1,
vin: {
$arrayElemAt: [
{ $map: { input: "$car_vins", in: "$$this._id" } }, 0
]
}
}
},
])
The result will look like this:
{ "make" : "Dodge", "vin" : ObjectId("626a12a1ac1cc4f9d0bbd7e7") }
{ "make" : "Honda", "vin" : ObjectId("626a12a1ac1cc4f9d0bbd7e8") }

Method 1
db.carDatas.aggregate([
{
$unwind: "$carData"
},
{
$lookup: {
from: "Vins",
localField: "carData.vin",
foreignField: "Vin",
as: "docs"
}
},
{
$project: {
make: "$carData.make",
vin: { $first: "$docs._id" },
_id: 0
}
},
{
$group: {
_id: null,
carData: { $push: "$$ROOT" }
}
},
{
$unset: "_id"
}
])
mongoplayground

Related

Mongoose query - groupBy category and get last 4 items of each category

I am struggling in Writing that fetches 4 Products of each category. What I have done is
exports.recentproducts = catchAsync(async (req, res, next) => {
const doc = await Product.aggregate([
{ $sort: { date: -1 } },
{
$replaceRoot: {
newRoot: {
$mergeObjects: [{ $arrayElemAt: ['$products', 0] }, '$$ROOT'],
},
},
},
{
$group: {
_id: '$productCategory',
products: { $push: '$$ROOT' },
},
},
{
$project: {
// pagination for products
products: {
$slice: ['$products', 4],
},
_id: 1,
},
},
{
$lookup: {
from: 'Shop',
localField: 'shopId',
foreignField: '_id',
as: 'shop',
},
},
]);
Document Model
const mongoose = require('mongoose');
var ProductSchema = mongoose.Schema({
title: {
type: String,
require: [true, 'Product must have a Title!'],
},
productCategory: {
type: String,
require: [true, 'Product must have a Category!'],
},
shopId: {
type: mongoose.Schema.ObjectId,
ref: 'Shop',
require: [true, 'Product must have a Shop!'],
},
});
var Product = mongoose.model('Product', ProductSchema);
module.exports = Product;
expected result---
result= [
{
productCategory: "Graphics",
products:[//4 products object here
{
must populate shop data
}
]
},
{
productCategory: "3d",
products:[//4 products object here]
},
//there are seven categories I have like that
]
The Code i have done is working fine but it has two problems
It does not populate shopId from Shop Model even through I have tried lookup
It does not sort products in descending order(does not sort by date)
There are few fixes in your implemented query,
$sort stage as it is,
$group stage as it is and moves to the second stage
$project stage as it is and move to third stage
$lookup with shop collection, pass products.shopId as localField
$project for merge shop object in products array
$map to iterate loop of products array
$filter to iterate loop of shop array return matching product
$arrayElemAt to get first element from above filtered result
$mergeOjects to merge current object with filtered shop object
const doc = await Product.aggregate([
{ $sort: { date: -1 } },
{
$group: {
_id: "$productCategory",
products: { $push: "$$ROOT" }
}
},
{
$project: {
products: { $slice: ["$products", 4] }
}
},
{
$lookup: {
from: "Shop",
localField: "products.shopId",
foreignField: "_id",
as: "shop"
}
},
{
$project: {
products: {
$map: {
input: "$products",
in: {
$mergeObjects: [
"$$this",
{
shop: {
$arrayElemAt: [
{
$filter: {
input: "$shop",
as: "s",
cond: { $eq: ["$$s._id", "$$this.shopId"] }
}
},
0
]
}
}
]
}
}
}
}
}
])
Playground
Query
in MongoDB 5 we can use $setWindowFields and $rank
partition by productCategory and sort by date descending
keep only rank <= 4 (4 latest products)
lookup to get the shop information
group by category and push all the information of product and shop
Test code here
Product.aggregate(
[{$setWindowFields:
{partitionBy:"$productCategory",
sortBy:{date:-1},
output:{rank:{$rank:{}}}}},
{$match:{rank:{$lte:4}}},
{$lookup:
{from:"Shop",
localField:"shopId",
foreignField:"_id",
as:"shop"}},
{$set:{shop:{$first:"$shop"}}},
{$group:{_id:"$productCategory", products:{$push:"$$ROOT"}}}])

MongoDB find all docs where field doesn't exists, plus if exists apply field operator ($max) condition

I am looking for a query for a $match stage in my aggregation which do almost the same, as in this question, but..
if field (named rank in my case) doesn't exists in document, add document to results
but if field, exists, apply $operator condition (in my case it's $max) to this field, and add all documents that suits this condition to the results.
MongoPlayground with example collection.
Result should be like this:
[
{
"method": 3,
"item": 1,
"rank": 3 //because it has field named rank, and suits condition {rank: $max}
},
{
"method": 4,
"item": 1 //we need this, because document doesn't have rank field at all
},
{
"method": 5,
"item": 1 //we need this, because document doesn't have rank field at all
}
]
Things, that I have tried already:
{
$match: {
$or: [
{item: id, rank: {$exists: true, $max: "$rank"}}, //id === 1
{item: id, rank: {$exists: false}} //id === 1
]
}
}
UPD: As for now, probably I don't limit with $match stage only, $project is also relevant after default match, so I could request every document during $match stage by id no matter, have the doc rank field or not, and then, during $project stage do a "separation" by rank $exists
Try this one:
db.collection.aggregate([
{
$match: {
item: id
}
},
{
$group: {
_id: "$item", //<- Change here your searching field
max: {
$max: "$rank" //<- Change here your field to apply $max
},
data: {
$push: "$$ROOT"
}
}
},
{
$unwind: "$data"
},
{
$match: {
$expr: {
$or: [
{
$eq: [
{
$type: "$data.rank"
},
"missing"
]
},
{
$eq: [
"$data.rank",
"$max"
]
}
]
}
}
},
{
$replaceWith: "$data"
}
])
MongoPlayground
I have found an answer, separated from #Valijon's method, but it's also based on the logic above. My query is:
db.collection.aggregate([
{
$match: {
item: id
}
},
{
$project: {
method: 1,
item: 1,
rank: {
$ifNull: [
"$rank",
0
]
}
}
},
{
$group: {
_id: "$item",
data: {
$addToSet: "$$ROOT"
},
min_value: {
$min: "$rank"
},
max_value: {
$max: "$rank"
}
}
},
{
$unwind: "$data"
},
{
$match: {
$or: [
{
$expr: {
$eq: [
"$data.rank",
"$max_value"
]
}
},
{
$expr: {
$eq: [
"$data.rank",
"$min_value"
]
}
},
]
}
}
])
My query is based on $project stage which gives the empty field value 0. It also could be -1, or any value that isn't used in collection. And then I separate results.
MongoPlayground

Get sum of values from unique docs in collection of duplicate docs in MongoDB

I am new to MongoDB and I am stuck in the below scenario.
I have a collection that contains duplicate docs.
I just want to get the sum of the property in each doc excluding the duplicate docs.
My Docs looks like this:
{"_id":"5dd629461fc50b782479ea90",
"referenceId":"5dd581f10859d2737965d23a",
"sellingId":"319723fb80b1a297cf0803abad9bc60787537f14a6a37d6e47",
"account_name":"mrfsahas1234",
"vendor_name":"testaccount2",
"action_type":"purchase",
"product_name":"Bottle",
"product_quantity":10,
"transactionId":"319723fb80b1a297cf0803abad9bc60787537f14a6a37d6e47",
"uid":"2019-11-20T17:39:17.405Z",
"createdAt":"2019-11-21T08:56:56.589+00:00",
"updatedAt":"2019-11-21T08:56:56.589+00:00","__v":0
},
{
"_id":"5dd629461fc50b782479ea90",
"referenceId":"5dd581f10859d2737965d23a",
"sellingId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"account_name":"mrfsahas1234",
"vendor_name":"testaccount2",
"action_type":"purchase",
"product_name":"Bottle",
"product_quantity":50,
"transactionId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"uid":"2019-11-20T17:39:17.405Z",
},
{
"_id":"5dd629461fc50b782479ea90",
"referenceId":"5dd581f10859d2737965d23a",
"sellingId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"account_name":"mrfsahas1234",
"vendor_name":"testaccount2",
"action_type":"purchase",
"product_name":"Bottle",
"product_quantity":50,
"transactionId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"uid":"2019-11-20T17:39:17.405Z",
},
Currently, I am doing this:
MaterialsTrack.aggregate([
{
$match: {
$and: [
{product_name: product_name},
{account_name: account_name},
{action_type: 'purchase'},
{uid:uid}
]
}
},
{
$group: {_id: "$sellingId", PurchseQuantity: {$sum: "$product_quantity"}}
},
])
It returns the sum of product_quantity all the matching docs (including the duplicate docs).
Current Output:
{_id: "320a9a2f814a45e01eb98344c9af708fa2864d81587e5914", PurchseQuantity:110}
Expected Output:
{_id: "320a9a2f814a45e01eb98344c9af708fa2864d81587e5914", PurchseQuantity:60}
I want to get the sum of only unique docs. How can I achieve it?
Thanks in advance!
You need to sum inside of the $group _id field, and then use the replaceRoot to achieve the the result you wanted.
MaterialsTrack.aggregate([
{
$match: {
$and: [
{
product_name: "Bottle"
},
{
account_name: "mrfsahas1234"
},
{
action_type: "purchase"
},
{
uid: "2019-11-20T17:39:17.405Z"
}
]
}
},
{
$group: {
_id: {
sellingId: "$sellingId",
PurchaseQuantity: {
$sum: "$product_quantity"
}
}
}
},
{
$replaceRoot: {
newRoot: {
_id: "$_id.sellingId",
PurchaseQuantity: "$_id.PurchaseQuantity"
}
}
}
]);
Sample Output:
[
{
"PurchaseQuantity": 50,
"_id": "320a9a2f814a45e01eb98344c9af708fa2864d81587e5914"
}
]
Playground:
https://mongoplayground.net/p/MOneCRiSlO0
What about adding $addToSet to your aggregations pipeline
MaterialsTrack.aggregate([
{
$match: {
$and: [
{product_name: product_name},
{account_name: account_name},
{action_type: 'purchase'},
{uid:uid}
]
}
},
{
$group: {_id: "$sellingId", PurchseQuantity: {$sum: "$product_quantity"},"list" : {$addToSet : "$list"}}
},
])

convert a $lookup result to an object instead of array

I'm doing a $lookup from a _id. So the result is always 1 document. Hence, I want the result to be an object instead an array with one item.
let query = mongoose.model('Discipline').aggregate([
{
$match: {
project: mongoose.Types.ObjectId(req.params.projectId)
},
},
{
$lookup: {
from: "typecategories",
localField: "typeCategory",
foreignField: "_id",
as: "typeCategory"
}
},
{
$project: {
title: 1, typeCategory: "$typeCategory[0]"
}
}
]);
This notation: "$typeCategory[0]" is not working. Is there any smart way of doing this?
You can just use $unwind. It deconstructs an array field from the input documents to output a document for each element
let query = mongoose.model('Discipline').aggregate([
{
$match: {
project: mongoose.Types.ObjectId(req.params.projectId)
},
},
{
$lookup: {
from: "typecategories",
localField: "typeCategory",
foreignField: "_id",
as: "typeCategory"
}
},
{$unwind: '$typeCategory'},
{
$project: {
title: 1, typeCategory: "$typeCategory"
}
}
]);
You can use $arrayElemAt in $project stage.
Syntax of $arrayElemAt is { $arrayElemAt: [ <array>, <idxexOfArray> ] }
like:
mongoose.model('Discipline').aggregate([
{
$match: {
project: mongoose.Types.ObjectId(req.params.projectId)
},
},
{
$lookup: {
from: "typecategories",
localField: "typeCategory",
foreignField: "_id",
as: "typeCategory"
}
},
{
$project: {
name: 1, typeCategory: {$arrayElemAt:["$typeCategory",0]}
}
}
]);
Use $first to return the first element in the array:
$project: {
title: 1,
typeCategory: {$first: "$typeCategory"}
}
For merging two collections:
{$replaceRoot: { newRoot: { $mergeObjects: [ { $arrayElemAt: [ "$typeCategory", 0 ] }, "$$ROOT" ] } } },
{ $project: { typeCategory: 0 } }

Filter array during publication

I have a document that looks as follows.
{
_id: "1234",
orderIds: [1,2,3,4,5]
}
I want to publish this document, however, I want to lookup and filter this reactively. With an aggregation in 3.2, it would be something like this:
var id = "1234"
db.users.aggregate([
{ $match: { _id: id } },
{ $unwind: '$orderIds' },
{
$lookup: {
from: 'orders',
as: 'orders',
localField: 'orderIds',
foreignField: '_id'
}
},
{ $match: { 'orders.status': { $ne: 'closed' } } },
{
$group: {
_id: "$_id",
orders: { $push: '$orders' }
}
}
])
However, I can't find a way to do this with plain publications, and mongo 2.6.7 that meteor ships with. Is there a way to do it?

Categories