Extract value from an array of objects in mongodb aggregate - mongodb

I am new to mongodb,
I'm trying to extract expectedDeliveryTime from the following data:
{
"from": "Giza",
"delivery_rule": [
{
"to": "Giza",
"expectedDeliveryTime": 3
},
{
"to": "Riyadh",
"expectedDeliveryTime": 2
}
]
}
I am trying to fetch expectedDeliveryTime WHERE from='Giza' and to='Riyadh'
MySQL equivalent would be SELECT expectedDeliveryTime FROM delivery_rules AS d WHERE d.from='Giza' AND d.to='Giza'
Below is part of my code
{
$lookup: {
from: 'Setting',
pipeline: [
{
$match: {
$expr: { $eq: ['$name', 'delivery_rules'] },
},
}
],
as: 'delivery_rules',
}
},
{
$addFields: {
delivery_rules: "$delivery_rules.value"
}
},
{ $unwind: '$delivery_rules' },
{
$addFields: {
delivery_rules: {
$filter: {
input: "$delivery_rules",
as: "rule",
cond: {
$eq: [
"$$rule.from",
"Giza"
]
},
}
}
}
},
{
$group: {
expectedDeliveryTime: { $first: '$delivery_rules' },
}
},
{
$project: {
_id: 0,
expectedDeliveryTime: 1,
}
}

You can solve this using simply $match and $unwind
db.collection.aggregate([
{
"$match": {
"from": "Giza"
}
},
{
"$unwind": "$delivery_rule"
},
{
"$match": {
"delivery_rule.to": "Riyadh"
}
},
{
$project: {
_id: 0,
expectedDeliveryTime: "$delivery_rule.expectedDeliveryTime"
}
}
])
Here is the Mongo playground for your reference.

Related

find available rooms querying to bookings with aggregation

I have two collections, and i want to find available rooms between two dates 2021-10-01T00:00:00.000Z and 2021-10-31T23:59:59.999Z
bookings
{from:Date, to:Date, room:ObjectId, status:Boolean}
rooms
{_id:ObjectId, code:String, status:Boolean}
Any idea?
aggregate
db.bookings.aggregate([
{
"$match": {
"$and": [
{
"from": {
"$lt": "2021-10-16T23:59:59.999Z"
}
},
{
"to": {
"$gt": "2021-10-13T23:59:59.999Z"
}
}
],
"status": true
}
},
{
"$group": {
"_id": "1",
"notAvailableRooms": {
$addToSet: "$room"
}
}
},
{
"$lookup": {
from: "rooms",
let: {
ids: "$notAvailableRooms"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$not: {
$in: [
"$_id",
"$$ids"
]
}
},
{
$eq: [
"$status",
true
]
}
]
}
}
}
],
as: "availableRooms"
}
},
{
"$project": {
"availableRooms": 1
}
}
])
mongoplayground

$match in lookup stage without removing local documents in mongoDB

Right now I have a lookup stage that matches a collection of answers with their respective questions. The problem I'm having is that on the $match if the question does not having a matching answer document in the lookup collection it is removed from the results of the aggregation. How can I avoid this?
{
$lookup: {
from: 'groupansphotos',
let: { question_id: '$questions._id' },
pipeline: [
{
$match: {
$expr: { $eq: ['$_id', '$$question_id'] },
},
},
{ $unwind: '$answers' },
{ $match: { 'answers.reported': { $lt: 1 } } },
{ $sort: { 'answers.helpful': -1 } },
{ $limit: 2 },
{
$project: {
_id: 0,
k: { $toString: '$answers._id' }, // k
v: '$$ROOT.answers', // v
},
},
],
as: 'answers',
},
},

How to find prev/next document after sort in MongoDB

I want to find prev/next blog documents whose publish date is closest to the input document.
Below is the document structure.
Collection Examples (blog)
{
blogCode: "B0001",
publishDate: "2020-09-21"
},
{
blogCode: "B0002",
publishDate: "2020-09-22"
},
{
blogCode: "B0003",
publishDate: "2020-09-13"
},
{
blogCode: "B0004",
publishDate: "2020-09-24"
},
{
blogCode: "B0005",
publishDate: "2020-09-05"
}
If the input is blogCode = B0003
Expected output
{
blogCode: "B0005",
publishDate: "2020-09-05"
},
{
blogCode: "B0001",
publishDate: "2020-09-21"
}
How could I get the output result? In sql, it seems using ROW_NUMBER can solve my problem, however I can't find a solution to achieve the feature in MongoDB. The alternate solution may be reference to this answer (But, it seems inefficient). Maybe using mapReduce is another better solutions? I'm confused at the moment, please give me some help.
You can go like following.
We need to compare existing date with given date. So I used $facet to categorize both dates
The original data should be one Eg : B0003. So that I just get the first element of the origin[] array to compare with rest[] array
used $unwind to flat the rest[]
Substract to get the different between both dates
Again used $facet to find previous and next dates.
Then combined both to get your expected result
NOTE : The final array may have 0<elements<=2. The expected result given by you will not find out whether its a prev or next date if there is a one element. So my suggestion is add another field to say which date it is as the mongo playground shows
[{
$facet: {
origin: [{
$match: { blogCode: 'B0001' }
}],
rest: [{
$match: {
$expr: {
$ne: ['$blogCode','B0001']
}
}
}]
}
}, {
$project: {
origin: {
$arrayElemAt: ['$origin',0]
},
rest: 1
}
}, {
$unwind: {path: '$rest'}
}, {
$project: {
diff: {
$subtract: [{ $toDate: '$rest.publishDate' },{ $toDate: '$origin.publishDate'}]
},
rest: 1,
origin: 1
}
}, {
$facet: {
prev: [{
$sort: {diff: -1}
},
{
$match: {
diff: {$lt: 0 }
}
},
{
$limit: 1
},
{
$addFields:{"rest.type":"PREV"}
}
],
next: [{
$sort: { diff: 1 }
},
{
$match: {
diff: { $gt: 0 }
}
},
{
$limit: 1
},
{
$addFields:{"rest.type":"NEXT"}
}
]
}
}, {
$project: {
combined: {
$concatArrays: ["$prev", "$next"]
}
}
}, {
$unwind: {
path: "$combined"
}
}, {
$replaceRoot: {
newRoot: "$combined.rest"
}
}]
Working Mongo playground
Inspire for the solution of varman proposed. I also find another way to solve my problem by using includeArrayIndex.
[
{
$sort: {
"publishDate": 1
},
},
{
$group: {
_id: 1,
root: {
$push: "$$ROOT"
}
},
},
{
$unwind: {
path: "$root",
includeArrayIndex: "rownum"
}
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$root",
{
rownum: "$rownum"
}
]
}
}
},
{
$facet: {
currRow: [
{
$match: {
blogCode: "B0004"
},
},
{
$project: {
rownum: 1
}
}
],
root: [
{
$match: {
blogCode: {
$exists: true
}
}
},
]
}
},
{
$project: {
currRow: {
$arrayElemAt: [
"$currRow",
0
]
},
root: 1
}
},
{
$project: {
rownum: {
prev: {
$add: [
"$currRow.rownum",
-1
]
},
next: {
$add: [
"$currRow.rownum",
1
]
}
},
root: 1
}
},
{
$unwind: "$root"
},
{
$facet: {
prev: [
{
$match: {
$expr: {
$eq: [
"$root.rownum",
"$rownum.prev"
]
}
}
},
{
$replaceRoot: {
newRoot: "$root"
}
}
],
next: [
{
$match: {
$expr: {
$eq: [
"$root.rownum",
"$rownum.next"
]
}
}
},
{
$replaceRoot: {
newRoot: "$root"
}
}
],
}
},
{
$project: {
prev: {
$arrayElemAt: [
"$prev",
0
]
},
next: {
$arrayElemAt: [
"$next",
0
]
},
}
},
]
Working Mongo playground

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

MongoDB Multi level $lookup sort not working

Multi level $lookup sort not working in aggregation.
Sorting works only for country, state name. Tried applying sorting for cities, but country sort overwrites the city sort.
Query2 is working but i don't want to sort collections inside lookup pipeline.
Is there any way to achieve all level of sorting(country,state,city) in Query1
Query1(Not Working):
Country.aggregate([
{
$lookup:{
from: 'states',
localField:'_id',
foreignField:'countryId',
as:'states'
}
},
{
$unwind: {
path: "$states",
preserveNullAndEmptyArrays: true
}
},
{
$sort: {
'states.name': 1
}
},
{
$lookup:{
from: 'cities',
localField:'states._id',
foreignField:'stateId',
as:'states.cities'
}
},
{
$sort: {
'states.cities.name': 1
}
},
{
$group: {
_id: {
_id: '$_id',
name: '$name'
},
states: {
$push: '$states'
}
}
},
{
$project: {
_id: '$_id._id',
name: '$_id.name',
states: 1
}
},
{
$sort: {
name: 1
}
}
])
Query2(Working):
Execution time is 8 times higher than Query1.
[
{
$lookup : {
from : 'states',
let: { 'countryId': '$_id' },
pipeline: [
{
$match: {
$expr:
{
$eq: ['$countryId', '$$countryId']
}
}
},
{
$sort : {
name : -1
}
}
],
as : 'states'
}
},
{
$unwind: {
path: '$states',
preserveNullAndEmptyArrays: true
}
},
{
$lookup : {
from : 'cities',
let: { 'stateId': '$states._id' },
pipeline: [
{
$match: {
$expr:
{
$eq: ['$stateId', '$$stateId']
}
}
},
{
$sort : {
name : -1
}
}
],
as : 'states.cities'
}
},
{
$group: {
_id: {
_id: '$_id',
name: '$name'
},
states: {
$push: '$states'
}
}
},
{
$project: {
_id: '$_id._id',
name: '$_id.name',
states: 1
}
}
]
In the newer $lookup syntax you do not need to use $unwind to join nested fields. You can easily use $lookup inside the pipeline to join multiple level.
[
{ "$lookup": {
"from": "states",
"let": { "countryId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$countryId", "$$countryId"] }}},
{ "$lookup": {
"from": "cities",
"let": { "stateId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$stateId", "$$stateId"] }}},
{ "$sort": { "name": -1 }}
],
"as": "cities"
}},
{ "$sort": { "name": -1 }}
],
"as": "states"
}}
]