mongo aggregate with $cond not working with $eq on nested lookups - mongodb

I am having issues with referencing a nested array item in a $cond statement.
db.getCollection('bookings').aggregate([
{
$lookup: {
from: "listings",
localField: "listingId",
foreignField: "_id",
as: "listing"
}
},
{
$match: {
$and: [
{
locationId: ObjectId("5c0f0c882fcf07fb08890c27")
},
{
$or: [
{
$and: [
{
state: "booked"
},
{
startDate: {
$lte: new Date()
}
},
{
startDate: {
$gte: ISODate("2019-12-18T07:00:00.000Z")
}
}
]
},
{
$and: [
{
listing: {
$elemMatch: {
inspectionStatus: "none"
}
}
},
{
endDate: {
$lte: new Date()
}
},
{
endDate: {
$gte: ISODate("2019-12-18T07:00:00.000Z")
}
},
{
state: {
$in: [
"active",
"returned"
]
}
}
]
},
{
$and: [
{
state: {
$ne: "cancelled"
}
},
{
$or: [
{
$and: [
{
startDate: {
$gte: ISODate("2019-12-20T07:00:00.993Z")
}
},
{
startDate: {
$lte: ISODate("2019-12-21T06:59:59.999Z")
}
}
]
},
{
$and: [
{
endDate: {
$gte: ISODate("2019-12-20T07:00:00.993Z")
}
},
{
endDate: {
$lte: ISODate("2019-12-21T06:59:59.999Z")
}
}
]
}
]
}
]
}
]
}
]
}
},
{
$addFields: {
isLate: {
$cond: [
{
$or: [
{
$and: [
{
$eq: [
"$listing.0.inspectionStatus",
"none"
]
},
{
$lte: [
"$endDate",
new Date()
]
},
{
$gte: [
"$endDate",
ISODate("2019-12-18T07:00:00.000Z")
]
},
{
$in: [
"$state",
[
"active",
"returned"
]
]
},
]
},
{
$and: [
{
$eq: [
"$state",
"booked"
]
},
{
$lte: [
"$startDate",
new Date()
]
},
{
$gte: [
"$startDate",
ISODate("2019-12-18T07:00:00.000Z")
]
}
]
}
]
},
true,
false
]
}
}
}
])
In the above, the following lines in the $cond statement does not work at all:
$eq: [
"$listing.0.inspectionStatus",
"none"
]
My question is - how do I make the above work? Note that there is always only one array item in the listing field after the lookup (never more than one array item in there). I've tried different variations like $listing.$0.$inspectionStatus - but nothing seems to work. I could go down the trajectory of researching group and filter - but I feel like this is overkill when I simply always want to access the first and only item in the listing array.

Please use $in keyword instead of $eq keyword in $cond keyword
db.demo1.aggregate([
{
$lookup: {
from: "demo2",
localField: "listingId",
foreignField: "_id",
as: "listing"
}
},
{
$match: {
$and: [
{
locationId: ObjectId("5c0f0c882fcf07fb08890c27")
},
{
$or: [
{
$and: [
{
state: "booked"
},
{
startDate: {
$lte: new Date()
}
},
{
startDate: {
$gte: ISODate("2019-12-18T07:00:00.000Z")
}
}
]
},
{
$and: [
{
listing: {
$elemMatch: {
inspectionStatus: "none"
}
}
},
{
endDate: {
$lte: new Date()
}
},
{
endDate: {
$gte: ISODate("2019-12-18T07:00:00.000Z")
}
},
{
state: {
$in: [
"active",
"returned"
]
}
}
]
},
{
$and: [
{
state: {
$ne: "cancelled"
}
},
{
$or: [
{
$and: [
{
startDate: {
$gte: ISODate("2019-12-20T07:00:00.993Z")
}
},
{
startDate: {
$lte: ISODate("2019-12-21T06:59:59.999Z")
}
}
]
},
{
$and: [
{
endDate: {
$gte: ISODate("2019-12-20T07:00:00.993Z")
}
},
{
endDate: {
$lte: ISODate("2019-12-21T06:59:59.999Z")
}
}
]
}
]
}
]
}
]
}
]
}
},
{
$addFields: {
isLate: {
$cond: [
{
$or: [
{
$and: [
{
$in: [
"none",
"$listing.inspectionStatus",
]
},
{
$lte: [
"$endDate",
new Date()
]
},
{
$gte: [
"$endDate",
ISODate("2019-12-18T07:00:00.000Z")
]
},
{
$in: [
"$state",
[
"active",
"returned"
]
]
},
]
},
{
$and: [
{
$eq: [
"$state",
"booked"
]
},
{
$lte: [
"$startDate",
new Date()
]
},
{
$gte: [
"$startDate",
ISODate("2019-12-18T07:00:00.000Z")
]
}
]
}
]
},
true,
false
]
}
}
}
])

Related

How to find and update a document in MongoDB

I am having a similar collection
db={
collectionA: [
{
"id": ObjectId("63b7c24c06ebe7a8fd11777b"),
"uniqueRefId": "UUID-2023-0001",
"products": [
{
"productIndex": 1,
"isProdApproved": false,
"productCategory": ObjectId("63b7c24c06ebe7a8fd11777b"),
"productOwners": [
{
_id: ObjectId("63b7c2fd06ebe7a8fd117781"),
iApproved: false
},
{
_id: ObjectId("63b7c2fd06ebe7a8fd117782"),
iApproved: false
}
]
},
{
"productIndex": 2,
"isProdApproved": false,
"productCategory": ObjectId("63b7c24c06ebe7a8fd11777b"),
"productOwners": [
{
_id: ObjectId("63b7c2fd06ebe7a8fd117781"),
iApproved: false
},
{
_id: ObjectId("63b7c2fd06ebe7a8fd117783"),
iApproved: false
}
]
},
{
"productIndex": 3,
"productCategory": "",
"productOwners": ""
}
]
}
]
}
I want to find the productOwner whose _id is 63b7c2fd06ebe7a8fd117781 in the productOwners and update the isApproved and isprodApproved to true. Other data will remain as it is.
I have tried this but it is only updating the first occurance
db.collectionA.update(
{
_id: ObjectId('63b7c24c06ebe7a8fd11777b'),
'products.productOwners._id': ObjectId('63b7c2fd06ebe7a8fd117781'),
},
{ $set: { 'products.$.productOwners.$[x].isApproved': true } },
{ arrayFilters: [{ 'x._id': ObjectId('63b7c2fd06ebe7a8fd117781') }] }
);
This one should work:
db.collection.updateMany({},
[
{
$set: {
products: {
$map: {
input: "$products",
as: "product",
in: {
$cond: {
if: { $eq: [{ $type: "$$product.productOwners" }, "array"] },
then: {
$mergeObjects: [
"$$product",
{ isProdApproved: { $in: [ObjectId("63b7c2fd06ebe7a8fd117781"), "$$product.productOwners._id"] } },
{
productOwners: {
$map: {
input: "$$product.productOwners",
as: 'owner',
in: {
$mergeObjects: [
"$$owner",
{ iApproved: { $eq: ["$$owner._id", ObjectId("63b7c2fd06ebe7a8fd117781")] } }
]
}
}
}
}
]
},
else: "$$product"
}
}
}
}
}
}
]
)
However, the data seem to be redundant. Better update only products.productOwners.iApproved and then derive products.isProdApproved from nested elements:
db.collection.aggregate([
{
$set: {
products: {
$map: {
input: "$products",
as: "product",
in: {
$cond: {
if: { $eq: [{ $type: "$$product.productOwners" }, "array"] },
then: {
$mergeObjects: [
"$$product",
{ isProdApproved: { $anyElementTrue: ["$$product.productOwners.iApproved"] } },
]
},
else: "$$product"
}
}
}
}
}
}
])

Mongoose fails to updateMany with $switch operator

I tried to use an example in the official document of mongodb,
db.students3.updateMany({ },
[
{ $set: { grade: { $switch: {
branches: [
{ case: { $gte: [ "$average", 90 ] }, then: "A" },
{ case: { $gte: [ "$average", 80 ] }, then: "B" },
{ case: { $gte: [ "$average", 70 ] }, then: "C" },
{ case: { $gte: [ "$average", 60 ] }, then: "D" }
],
default: "F"
} } } }
])
Property grade is defined as Number type. I got the error, when tried to update all documents in the database students3,
CastError: Cast to Number failed for value "{ '$switch': { branches: [ [Object] ] } }" (type Object) at path "grade"
Could someone explain the error?
Thanks.
You have to $trunc first, then average field would be created.
{ $set: { average : { $trunc: [ { $avg: "$tests" }, 0 ] }, modified: "$$NOW" } }
complete update
db.collection.update({},
[
{
$set: {
average: { $trunc: [ { $avg: "$tests" }, 0 ] },
modified: "$$NOW"
}
},
{
$set: {
grade: {
$switch: {
branches: [
{ case: { $gte: [ "$average", 90 ] }, then: "A" },
{ case: { $gte: [ "$average", 80 ] }, then: "B" },
{ case: { $gte: [ "$average", 70 ] }, then: "C" },
{ case: { $gte: [ "$average", 60 ] }, then: "D" }
],
default: "F"
}
}
}
}
])
mongoplayground

mongodb lookup using multiple fields not working

I am trying to get some other information from titleInfo collection using siteId of regCodes collection
I have two collections
regCodes
{
"siteId" : "123A",
"registration_code" : "ABC",
"used_flag" : true,
"Allowed_Use" : 1,
"Remaining_Use" : 0,
"BatchId" : "SNGL",
"CodeDuration" : 180
}
titleInfo
{
"title" : "Principles of Microeconomics",
"product_form_detail" : "EPUB",
"final_binding_description" : "Ebook",
"vitalsource_enabled" : false,
"reading_line" : "with InQuizitive and Smartwork5",
"volume" : "",
"protected_content" : {
"ebookSiteIds" : [
"123A"
],
"studySpaceSiteIds" : [],
"iqSiteIds" : []
}
}
below query not working, getting 'regcodeData' as empty array.
using mongodb version 3.6.18
db.getCollection('regCodes').aggregate([
{
$match: {
registration_code: 'ABC'
}
},
{
$lookup: {
from: "titleInfo",
let: {
regcode_siteId: "$siteId"
},
pipeline: [
{
$match: {
$expr: {
$or: [
{
$eq: [
"$protected_content.ebookSiteIds",
"$$regcode_siteId"
]
},
{
$eq: [
"$protected_content.studySpaceSiteIds",
"$$regcode_siteId"
]
},
{
$eq: [
"$protected_content.iqSiteIds",
"$$regcode_siteId"
]
}
]
}
}
}
],
as: "regcodeData"
}
}
])
below query is working as expected
db.getCollection('titleInfo').find({
$or: [
{
"protected_content.ebookSiteIds": "123A"
},
{
"protected_content.studySpaceSiteIds": "123A"
},
{
"protected_content.iqSiteIds": "123A"
}
]
})
You just need to unwind the arrays, by using $unwind operator with preserveNullAndEmptyArrays option set to true.
Updated Query:
db.regCodes.aggregate([
{
$match: {
registration_code: "ABC"
}
},
{
$lookup: {
from: "titleInfo",
let: {
regcode_siteId: "$siteId"
},
pipeline: [
{
$unwind: {
path: "$protected_content.ebookSiteIds",
preserveNullAndEmptyArrays: true
}
},
{
$unwind: {
path: "$protected_content.studySpaceSiteIds",
preserveNullAndEmptyArrays: true
}
},
{
$unwind: {
path: "$protected_content.iqSiteIds",
preserveNullAndEmptyArrays: true
}
},
{
$match: {
$expr: {
$or: [
{
$eq: [
"$protected_content.ebookSiteIds",
"$$regcode_siteId"
]
},
{
$eq: [
"$protected_content.studySpaceSiteIds",
"$$regcode_siteId"
]
},
{
$eq: [
"$protected_content.iqSiteIds",
"$$regcode_siteId"
]
}
]
}
}
}
],
as: "regcodeData"
}
}
])
MongoPlayGroundLink
My bad trying to match array with string
Answer is as below
db.getCollection('regCodes').aggregate([
{
$match: {
registration_code: 'ABC'
}
},
{
$lookup: {
from: "titleInfo",
let: {
regcode_siteId: "$siteId"
},
pipeline: [
{
$match: {
$expr: {
$or: [
{
$in: [
"$$regcode_siteId",
"$protected_content.ebookSiteIds"
]
},
{
$in: [
"$$regcode_siteId",
"$protected_content.studySpaceSiteIds"
]
},
{
$in: [
"$$regcode_siteId",
"$protected_content.iqSiteIds"
]
}
]
}
}
}
],
as: "regcodeData"
}
}
])

how to use $groupby and transform distinct value mongodb

How to transform the data using $if $else groupby condition MongoDB?
This playground should return two object who belongs to text with "tester 2" and "tester 3" also if I have multiple object in history collection it should also check with last object not will all object how it is possible
So condition should say if history's date is $gt then main collection should return nothing else return the matched criteria data.
db.main.aggregate([
{
$lookup: {
from: "history",
localField: "history_id",
foreignField: "history_id",
as: "History"
}
},
{
$unwind: "$History"
},
{
"$match": {
$expr: {
$cond: {
if: {
$eq: [
"5e4e74eb380054797d9db623",
"$History.user_id"
]
},
then: {
$and: [
{
$gt: [
"$date",
"$History.date"
]
},
{
$eq: [
"5e4e74eb380054797d9db623",
"$History.user_id"
]
}
]
},
else: {}
}
}
}
}
])
MongoPlayground
If I understand you correctly, it is what you are trying to do:
db.main.aggregate([
{
$lookup: {
from: "history",
let: {
main_history_id: "$history_id",
main_user_id: { $toString: "$sender_id" }
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$history_id",
"$$main_history_id"
]
},
{
$eq: [
"$user_id",
"$$main_user_id"
]
}
]
}
}
}
],
as: "History"
}
},
{
$unwind: {
path: "$History",
preserveNullAndEmptyArrays: true
}
},
{
$sort: {
_id: 1,
"History.history_id": 1,
"History.date": 1
}
},
{
$group: {
_id: "$_id",
data: { $last: "$$ROOT" },
History: { $last: "$History" }
}
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$data",
{ History: "$History" }
]
}
}
},
{
"$match": {
$expr: {
$or: [
{
$eq: [
{ $type: "$History.date" },
"missing"
]
},
{
$ne: [
"5e4e74eb380054797d9db623",
"$History.user_id"
]
},
{
$and: [
{
$eq: [
"5e4e74eb380054797d9db623",
"$History.user_id"
]
},
{
$gte: [
"$date",
"$History.date"
]
}
]
}
]
}
}
}
])
MongoPlayground

MongoDB - group by on facet result

Provided I have following collections
Customers
[
{
uuid: "first",
culture: "it-it"
},
{
uuid: "second",
culture: "de-de"
}
]
Vehicles
[
{
model: "mymodel",
users: [
{
uuid: "first",
isOwner: true,
createdOn: "2019-05-15T06: 00: 00"
}
]
},
{
model: "mymodel",
users: [
{
uuid: "first",
isOwner: false,
createdOn: "2019-05-15T06: 00: 00"
},
{
uuid: "second",
isOwner: true,
createdOn: "2019-05-15T06: 00: 00"
}
]
}
]
And following query:
db.customers.aggregate([
{
$lookup: {
from: "vehicles",
let: {
uuid: "$uuid"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$$uuid",
"$users.uuid"
]
}
}
},
{
$project: {
model: 1,
users: {
$filter: {
input: "$users",
as: "user",
cond: {
$eq: [
"$$user.uuid",
"$$uuid"
]
}
}
}
}
},
{
$unwind: "$users"
},
{
$replaceRoot: {
newRoot: {
isOwner: "$users.isOwner",
createdOn: "$users.createdOn"
}
}
}
],
as: "vehicles"
}
},
{
$facet: {
"createdOn": [
{
$match: {
"vehicles": {
$elemMatch: {
isOwner: true,
$and: [
{
"createdOn": {
$gte: "2019-05-15T00: 00: 00"
}
},
{
"createdOn": {
$lt: "2019-05-16T00: 00: 00"
}
}
]
}
}
}
},
{
$project: {
culture: 1,
count: {
$size: "$vehicles"
}
}
},
{
$group: {
_id: 0,
"total": {
$sum: "$count"
}
}
}
]
}
},
{
$project: {
"CreatedOn": {
$arrayElemAt: [
"$CreatedOn.total",
0
]
}
}
}
])
I get following result:
[
{
"createdOn": 2
}
]
What I would like to achieve is a result as follows:
[
{
culture: "it-it",
results: {
"createdOn": 1
}
},
{
culture: "de-de",
results: {
"createdOn": 1
}
}
]
But I cannot seem to figure out where I can group so that I can get that result.
Can someone show me the way to do this?
The query is more complex with more metrics so this is a trimmed down version of what I have.
I tried grouping everywhere but fail to get the desired result I want.
The following query can get us the expected output:
db.customers.aggregate([
{
$lookup: {
"from": "vehicles",
"let": {
"uuid": "$uuid"
},
"pipeline": [
{
$unwind: "$users"
},
{
$match: {
$expr: {
$and: [
{
$eq: ["$users.uuid", "$$uuid"]
},
{
$eq: ["$users.isOwner", true]
},
{
$gte: ["$users.createdOn", "2019-05-15T00: 00: 00"]
},
{
$lte: ["$users.createdOn", "2019-05-16T00: 00: 00"]
}
]
}
}
},
{
$count:"totalVehicles"
}
],
as: "vehiclesInfo"
}
},
{
$unwind: {
"path": "$vehiclesInfo",
"preserveNullAndEmptyArrays": true
}
},
{
$group: {
"_id": "$culture",
"culture": {
$first: "$culture"
},
"createdOn": {
$sum: "$vehiclesInfo.totalVehicles"
}
}
},
{
$project: {
"_id": 0,
"culture": 1,
"results.createdOn": "$createdOn"
}
}
]).pretty()
Output:
{ "culture" : "de-de", "results" : { "createdOn" : 1 } }
{ "culture" : "it-it", "results" : { "createdOn" : 1 } }