Mongo Sort Aggregation not working on Sub Document - mongodb

Trying to sort the sub-key on the document.
Example of data from the pipeline. Incorrect sort order for subkey availability.startIso
{
"_id": "60e458d7b896de9c8e44d6c9",
"uid": "6233ed1d8b154aa79d1435b5",
"name": "Pale",
"phoneNumber": "+19999813917",
"profileMedia": {
"url": "https://storage.googleapis.com/refresh-me-dev.appspot.com/dummy_photos/dummy_1.jpg",
"type": "photo"
},
"createdIso": "2021-07-06T13:21:27.513Z",
"isDeleted": false,
"isFlagged": false,
"isBanned": false,
"isAdmin": false,
"isVendor": true,
"lastOpenedAppIso": "2021-07-06T13:21:27.513Z",
"vendorMeta": {
"servicesOffered": [
"swedish"
],
"location": [
0,
0
]
},
"distanceFromPoint": 0,
"availability": [
{
"_id": "60e458d7b896de9c8e44d6cc",
"uid": "dec97d4b1dea44f7b2fa45a5",
"vendorUid": "6233ed1d8b154aa79d1435b5",
"startIso": "2021-07-12T04:07:21.349Z",
"endIso": "2021-07-12T05:07:21.360Z"
},
{
"_id": "60e458d7b896de9c8e44d6ce",
"uid": "a5928ea5c18c4321bd6a9a9b",
"vendorUid": "6233ed1d8b154aa79d1435b5",
"startIso": "2021-07-11T01:52:18.323Z",
"endIso": "2021-07-11T02:52:18.335Z"
}
]
}
Example of the aggregation
let vendors = await mongoDb
.collection<User>(collectionNames.users)
.aggregate([
{
$geoNear: {
near: { type: "Point", coordinates: [lat, lng] },
spherical: true,
maxDistance: 7500,
distanceField: "distanceFromPoint",
},
},
{
$match: { isVendor: true },
},
{
$match: { "vendorMeta.servicesOffered": { $in: services } },
},
{
$lookup: {
from: "vendor.availability",
localField: "uid",
foreignField: "vendorUid",
as: "availability",
},
},
{
$addFields: {
availability: {
$filter: {
input: "$availability",
as: "availability",
cond: { $and: [{ $gte: ["$$availability.startIso", nowIso] }, { $lte: ["$$availability.endIso", nDaysIso] }] },
},
},
},
},
{ $sort: { "availability.startIso": 1 } },
{ $match: { availability: { $ne: [] } } },
])
.toArray();

This is working as intended, $sort does not work on arrays and can't be used like this. What you can do is $unwind, then $sort and end by $grouping to restore the structure, like so:
[
// ...,
{
$unwind: "$availability"
},
{ $sort: { "availability.startIso": 1 } },
{
$group: {
_id: '$_id',
root: {$first: "$$ROOT"},
availability: {$push: '$availability'}
}
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
'$root',
{ availability: '$availability'}
]
}
}
}
]
Note that i removed the :
{ $match: { availability: { $ne: [] } } },
As it's no longer required because $unwind will remove those documents for you.

Related

MongoDB Aggregation - Select only same value from array inside lookup

I have aggregation like this:
Produk.aggregate([
{
$lookup: {
from: "kis_m_kategoriproduks",
localField: "idSubKategori",
foreignField: "subKategori._id",
as: "kategori",
},
},
{ $unwind: "$kategori" },
{ $sort: { produk: 1 } },
{
$project: {
_id: 0,
id: "$id",
dataKategori: {
idKategori: "$kategori._id",
kategori: "$kategori.kategori",
idSubKategori: "$idSubKategori",
subKategori: "$kategori.subKategori",
},
},
},
])
current result is :
{
"status": "success",
"data": [
{
"dataKategori": {
"idKategori": "6195bbec8ee419e6a9b8329d",
"kategori": "Kuliner",
"idSubKategori": "6195bc0f8ee419e6a9b832a2",
"subKategori": [
{
"nama": "Food",
"_id": "6195bc0f8ee419e6a9b832a2"
},
{
"nama": "Drink",
"_id": "6195bc258ee419e6a9b832a8"
}
]
}
}
]
}
I only want to display data in subKategori that the _id match with idSubKategori. this what I expected:
{
"status": "success",
"data": [
{
"dataKategori": {
"idKategori": "6195bbec8ee419e6a9b8329d",
"kategori": "Kuliner",
"idSubKategori": "6195bc0f8ee419e6a9b832a2",
"subKategori": [
{
"nama": "Food",
"_id": "6195bc0f8ee419e6a9b832a2"
}
]
}
}
]
}
here is my $kategori schema:
const schema = mongoose.Schema(
{
kategori: {
type: String,
required: true,
unique: true,
},
subKategori: [
{
id: mongoose.Types.ObjectId,
nama: String,
},
],
},
{
timestamps: false,
}
);
any suggestion?
I fix the problem by add $filter inside $project like this:
dataKategori: {
idKategori: "$kategori._id",
kategori: "$kategori.kategori",
subKategori: {
$arrayElemAt: [
{
$filter: {
input: "$kategori.subKategori",
as: "sub",
cond: { $eq: ["$$sub._id", "$idSubKategori"] },
},
},
0,
],
},
},
reference: https://stackoverflow.com/a/42490320/6412375

MongoDB Count With Condition within Project with $eq

I'm trying to count my "$attendance.status" with aggregation mongodb.
I've get my data with relations. then i want to count by relation columns like 'present', 'off', etc.
code
Employee.aggregate([
{
$lookup: {
from: "Attendance",
let: { employeeId: "$_id" },
pipeline: [
{
$match: {
$and: [
{ $expr: { $eq: ["$employeeId", "$$employeeId"] } },
{ isApproved: true },
{
createdAt: {
$gte: startOfMonth.toDate(),
$lte: endOfMonth.toDate(),
},
},
],
},
},
],
as: "attendance",
},
},
{
$project: {
_id: 1,
username: 1,
name: 1,
attendance: 1,
present: { $sum: { $eq: ["$attendance.status", "present"] } },
},
},
]);
But why cannot count my column?
i use $eq, with $sum then count the result. but the result is 0
{
"username": "Ethyl",
"name": "Kuhn",
"id": "614d43cde735f3e601dea165",
"attendance": [
{
"_id": "614d43cde735f3e601dea16f",
"status": "present",
"start": "2021-09-24T03:19:41.645Z",
"employeeId": "614d43cde735f3e601dea165",
"isApproved": true
},
],
"present": 0,
"sick": 0,
"off": 0,
},

How to use `$unwind` to iterate exact value

I am new to mongoose and mongodb.
In my addtocart schema I have added $lookups and projection to populate products in add to cart.
In current response product_purchase_quantity it was in array in add to cart collection it is key and value. So, in $lookups I tried to add {$unwind: '$product_purchase_quantity'} but after adding add_to_cart_products object prints twice. I don't know where the mistake was?
Below i have mentioned expected result.
AddToCart Schema:
lookups: [
{
from: 'shop_db_products',
let: {
productId: '$add_to_cart_products.product',
purchaseQuantity: '$add_to_cart_products.product_quantity',
productItemId: '$add_to_cart_products.product_item',
},
pipeline: [
{
$match: { $expr: { $in: ['$_id', '$$productId'] } },
},
{
$lookup: {
from: 'shop_db_products',
localField: 'product_id',
foreignField: '_id',
as: 'products',
},
},
{
$project: {
_id: true,
product: {
_id: '$_id',
product_name: '$product_name',
},
product_purchase_quantity: '$$purchaseQuantity',
product_item: {
$reduce: {
input: {
$filter: {
input: '$product_items',
cond: {
$in: ['$$this._id', '$$productItemId'],
},
},
},
initialValue: {},
in: {
_id: '$$this._id',
product_size: { $concat: [{ $toString: '$$this.product_size.value' }, '$$this.product_size.unit'] },
product_price: '$$this.product_price',
product_type: '$$this.product_type'
},
},
},
},
},
],
localField: '',
as: 'add_to_cart_products',
model: 'ProductModel',
},
],
AddToCart Collection
[
{
"add_to_cart_user": "5f0076b7bd530928fc0c0285",
"add_to_cart_products": [
{
"product": "5f05a0270b4f3a5c41c70826",
"product_item": "5f05a0270b4f3a5c41c70877",
"product_quantity": 5
},
{
"product": "5f05a0270b4f3a5c41c70827",
"product_item": "5f05a0270b4f3a5c41c70666",
"product_quantity": 3
}
],
"add_to_cart_product_total": 5,
"add_to_cart_discount": 50,
"add_to_tax": "5eae321d21924800122f978e",
"add_to_cart_grand_total": 500
}
]
Current Response:
{
"_id": "5fa2a09b3c6316482098f6ff",
"add_to_cart_status_is_active": true,
"add_to_cart_discount": 50,
"add_to_cart_tax": 8,
"add_to_cart_products": [
{
"product": {
"_id": "5f05a0270b4f3a5c41c70826",
"product_name": "Avery Apricot Sour"
},
"product_purchase_quantity": [
5,
3
],
"product_item": {
"_id": "5f05a0270b4f3a5c41c70877",
"product_size": "22oz",
"product_price": 13.99
}
},
{
"product": {
"_id": "5f05a0270b4f3a5c41c70827",
"product_name": "Avery Dugana"
},
"product_purchase_quantity": [
5,
3
],
"product_item": {
"_id": "5f05a0270b4f3a5c41c70666",
"product_size": "22oz",
"product_price": 8.99
}
}
]
}
Expected response:
{
"_id": "5fa2a09b3c6316482098f6ff",
"add_to_cart_status_is_active": true,
"add_to_cart_discount": 50,
"add_to_cart_tax": 8,
"add_to_cart_products": [
{
"product": {
"_id": "5f05a0270b4f3a5c41c70826",
"product_name": "Avery Apricot Sour"
},
"product_purchase_quantity": 5,
"product_item": {
"_id": "5f05a0270b4f3a5c41c70877",
"product_size": "22oz",
"product_price": 13.99
}
},
{
"product": {
"_id": "5f05a0270b4f3a5c41c70827",
"product_name": "Avery Dugana"
},
"product_purchase_quantity": 3,
"product_item": {
"_id": "5f05a0270b4f3a5c41c70666",
"product_size": "22oz",
"product_price": 8.99
}
}
]
}
Product (shop_db_products) collection:
[
{
"_id": "5f05a0270b4f3a5c41c70826",
"product_no": "PRO04087",
"product_store_no": "1001",
"product_dept_no": "Irish Whiskey",
"product_name": "2Gingers",
"product_overview": "No Overview",
"product_items": [
{
"_id": "5f05a0270b4f3a5c41c70877",
"product_item_number": "857566003019",
"product_price": 20.99,
"product_cost": 20.99,
"product_size": "750ml",
"product_type": "Bottle",
"product_value": 0,
"product_quantity": 0,
"product_images": [
"pro04087-857566003019-1.png"
],
"product_item_is_active": true
}
]
},
...
]
You have to $unwind add_to_cart_products before performing $lookup
db.addToCart.aggregate([
{
$unwind: "$add_to_cart_products"
},
{
$lookup: {
from: "shop_db_products",
let: {
productId: "$add_to_cart_products.product",
purchaseQuantity: "$add_to_cart_products.product_quantity",
productItemId: "$add_to_cart_products.product_item"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$_id",
"$$productId"
]
}
}
},
{
$project: {
_id: true,
product: {
_id: "$_id",
product_name: "$product_name",
},
product_purchase_quantity: "$$purchaseQuantity",
product_item: {
$reduce: {
input: {
$filter: {
input: "$product_items",
cond: {
$eq: [
"$$this._id",
"$$productItemId"
],
},
},
},
initialValue: {},
in: {
_id: "$$this._id",
product_size: "$$this.product_size",
product_price: "$$this.product_price",
product_type: "$$this.product_type"
}
}
}
}
}
],
as: "add_to_cart_products"
}
},
{
$unwind: "$add_to_cart_products"
},
{
$group: {
_id: "$_id",
"add_to_cart_products": {
$push: "$add_to_cart_products"
}
}
}
])
MongoDB Playground
Another way
Without reducer, with $unwind
https://mongoplayground.net/p/3uWA5pVBv83
db.addToCart.aggregate([
{
"$unwind": "$add_to_cart_products"
},
{
"$lookup": {
"from": "shop_db_products",
"let": {
"productId": "$add_to_cart_products.product",
"purchaseQuantity": "$add_to_cart_products.product_quantity",
"productItemId": "$add_to_cart_products.product_item"
},
"pipeline": [
{
"$match": {
"$expr": {
"$eq": [
"$_id",
"$$productId"
]
}
}
},
{
"$unwind": "$product_items"
},
{
"$match": {
"$expr": {
"$eq": [
"$product_items._id",
"$$productItemId"
]
}
}
},
{
"$project": {
"_id": true,
"product": {
"_id": "$_id",
"product_name": "$product_name"
},
"product_purchase_quantity": "$$purchaseQuantity",
"product_item": "$product_items"
}
}
],
"as": "productResolved"
}
},
{
"$unwind": "$productResolved"
},
{
"$group": {
"_id": "$_id",
"add_to_cart_products": {
"$push": "$productResolved"
}
}
}
])

How to use project in nested array using $In to get Data

I want to get to use array for searching data in mongo
Here is my data
{
"_id": ObjectID("5f0d6e394556410d486f6828"),
"branchId": "exampleId",
"info": [
{
"date": "12-10-09",
"programs": [
{
"programId": "ObjectId1",
},
{
"programId": "ObjectId2",
},
{
"programId": "ObjectId3",
},
],
}
],
}
I want to find programId by using array here is my array
const programIdArr = ["ObjectId1","ObjectId2"]
I want out put
NOTE : I want to use programIdArr search in mySql we can use something like whereIn
{
"_id": ObjectID("5f0d6e394556410d486f6828"),
"branchId": "exampleId",
"info": [
{
"date": "12-10-09",
"programs": [
{
"programId": "ObjectId1",
},
{
"programId": "ObjectId2",
},
],
}
],
}
Here is what I try to do
const program =
Models.aggregate([
{ $match: { _id: Mongoose.Types.ObjectId('5f0d6e394556410d486f6828') } },
{
//for first project I already get where date = 12-10-09
$project: {
info: {
$filter: {
input: '$info',
as: 'info',
cond: { $eq: ['$$info.date', '12-10-09'] },
},
},
},
// in this project I try to filter programId by using programIdArr It doesn't work
$project: {
info: {
$filter: {
input: '$info.programs.programId',
as: 'programId',
cond: { $in: [programIdArr] },
},
},
},
},
{ $unwind: { path: '$info' } },
])
.exec()
As I read document $in it's return only true of false I try also try to use $all but It didn't work too.
You can try this
[{
$match: {
_id: ObjectId("5f0d6e394556410d486f6828")
}
}, {
$unwind: {
path: "$info",
preserveNullAndEmptyArrays: false
}
}, {
$project: {
branchId: 1,
"info.date": 1,
"info.programs": {
$filter: {
input: '$info.programs',
as: 'p',
cond: {
$in: ["$$p.programId", ["ObjectId1", "ObjectId2"]]
},
},
}
}
}, {
$group: {
_id: "$_id",
branchId: {
$first: "$branchId"
},
info: {
$addToSet: "$info"
}
}
}]
Working Mongo playground

Nested $addFields in MongoDB

I have the following document:
[
{
"callId": "17dac51e-125e-499e-9064-f20bd3b1a9d8",
"caller": {
"firstName": "Test",
"lastName": "Testing",
"phoneNumber": "1231231234"
},
"inquiries": [
{
"inquiryId": "b0d14381-ce75-49aa-a66a-c36ae20b72a8",
"routeHistory": [
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-01T06:00:00.000Z",
"status": "routed"
},
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-03T06:00:00.000Z",
"status": "routed"
}
]
},
{
"inquiryId": "9d743be9-7613-46d7-8f9b-a04b4b899b56",
"routeHistory": [
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-01T06:00:00.000Z",
"status": "ended"
},
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-03T06:00:00.000Z",
"status": "ended"
}
]
}
]
}
]
And I'm using the following aggregate:
{
$unwind: '$inquiries',
},
{
$addFields: {
'inquiries.routeHistory': {
$filter: {
input: '$inquiries.routeHistory',
cond: {
$eq: [{ $max: '$inquiries.routeHistory.routeDate' }, '$$this.routeDate'],
},
},
},
},
},
{
$group: {
_id: '$_id',
callId: { $first: '$callId' },
caller: { $first: '$caller' },
inquiries: { $push: '$inquiries' },
},
}
I would like to expand this query to be able to further filter at the inquiry grain, so that I am returning only the inquiry that contains my specified criteria. E.g. if I wanted to find where inquiry.routeHistory.status = ended, I would expect the following results:
[
{
"callId": "17dac51e-125e-499e-9064-f20bd3b1a9d8",
"caller": {
"firstName": "Test",
"lastName": "Testing",
"phoneNumber": "1231231234"
},
"inquiries": [
{
"inquiryId": "9d743be9-7613-46d7-8f9b-a04b4b899b56",
"routeHistory": [
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-03T06:00:00.000Z",
"status": "ended"
}
]
}
]
}
]
Is there a way to do nested $addField or is there another route I could take?
Since you're using $unwind you can do that easily by adding $match since expression: "inquiries.routeHistory.status": "ended" will return true if there's any document in routeHistory having such status:
db.collection.aggregate([
{
$unwind: "$inquiries"
},
{
$match: {
"inquiries.routeHistory.status": "ended"
}
},
{
$addFields: {
"inquiries.routeHistory": {
$filter: {
input: "$inquiries.routeHistory",
cond: {
$eq: [ { $max: "$inquiries.routeHistory.routeDate" }, "$$this.routeDate" ]
}
}
}
}
},
{
$group: {
_id: "$_id",
callId: { $first: "$callId" },
caller: { $first: "$caller" },
inquiries: { $push: "$inquiries" }
}
}
])
Mongo Playground