mongodb lookup using multiple fields not working - mongodb

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"
}
}
])

Related

MongoDB - Match inside $lookup pipeline for key matching not working

I am stuck in a MongoDB query. I have two collections.
plans Collection:
{
planName: 'TV1',
planFeatures: {
'AA1': true,
'AA2 : false',
'AA3': true,
'AA4': true,
'AA5': false
}
}, {
planName: 'TV2',
planFeatures: {
'AA1': true,
'AA2 : false',
'AA3': false,
'AA4': true,
'AA5': false
}
}, ..........
planFeatures Collection
{
key: 'AA1',
label: 'ALPHA',
}, {
key: 'AA2',
label: 'BETA'
}, ..........
I am trying to aggregate the 2 collections, in such a way that a key Name of planFeatures field in plans collections should be equal to key field of planFeatures collections
e.g. (key name of plans.planFeatures) == (planFeatures.key)
AA1 == AA1
PaymentPlanFeatures.aggregate([
{
$lookup: {
from: 'plans',
let: { 'kkey': '$key'},
pipeline: [
{ $set: { 'planFeatures5': { '$objectToArray': '$planFeatures' }}},
{
$match: {
$expr: {
$eq: [
'$planFeatures5.k', '$$kkey'
]
}
}
}
],
as: 'paymentplans'
}
}
])
I want my result should look like this
[
{
"key": "AA1",
"label": "ALPHA",
"paymentplans": [
{
"planName": "TV1",
"planFeatures": {
"AA1": true,
"AA2": true,
"AA3": false
},
"__v": 0,
"planFeatures5": [
{
"k": "AA1",
"v": true
}
]
}
]
}, ..............
]
I am stuck on this issue for a long.
Can anybody help me please?
$planFeatures5.k return array of string, you should use $in operator for $match stage in $lookup pipeline.
{
$in: [
"$$kkey",
"$planFeatures5.k"
]
}
db.planFeatures.aggregate([
{
$lookup: {
from: "plans",
let: {
"kkey": "$key"
},
pipeline: [
{
$set: {
"planFeatures5": {
"$objectToArray": "$planFeatures"
}
}
},
{
$match: {
$expr: {
$in: [
"$$kkey",
"$planFeatures5.k"
]
}
}
}
],
as: "paymentplans"
}
}
])
Sample Mongo Playground

How to remove field conditionally mongoodb

I have a collection and its documents look like:
{
_id: ObjectId('111111111122222222223333'),
my_array: [
{
id: ObjectId('777777777788888888889999')
name: 'foo'
},
{
id: ObjectId('77777777778888888888555')
name: 'foo2'
}
//...
]
//more attributes
}
However, some documents have my_array: [{}] (with one element which is an empty array).
How can I add conditionally a projection or remove it?
I have to add it to a mongo pipeline at the end of the query, and I want to get my_array only when it has at least one element which is not an empty object. If there's an empty object remove it.
I tried with $cond and $eq in a projection stage but it is not supported. Any suggestion to solve this?
Suppose you have documents like this with my_array field:
{ "my_array" : [ ] }
{ "my_array" : [ { "a" : 1 } ] } // #(1)
{ "my_array" : null }
{ "some_fld" : "some value" }
{ "my_array" : [ { } ] }
{ "my_array" : [ { "a" : 2 }, { "a" : 3 } ] } // #(2)
And, the following aggregation will filter and the result will have the two documents (1) and (2):
db.collection.aggregate([
{
$match: {
$expr: {
$and: [
{ $eq: [ { $type: "$my_array" }, "array" ] },
{ $gt: [ { $size: "$my_array" }, 0 ] },
{ $ne: [ [{}], "$my_array" ] }
]
}
}
}
])
This also works with a find method:
db.collection.find({
$expr: {
$and: [
{ $eq: [ { $type: "$my_array" }, "array" ] },
{ $gt: [ { $size: "$my_array" }, 0 ] },
{ $ne: [ [{}], "$my_array" ] }
]
}
})
To remove the my_array field, from a document when its empty, then you try this aggregation:
db.collection.aggregate([
{
$addFields: {
my_array: {
$cond: [
{$and: [
{ $eq: [ { $type: "$my_array" }, "array" ] },
{ $gt: [ { $size: "$my_array" }, 0 ] },
{ $ne: [ [{}], "$my_array" ] }
]},
"$my_array",
"$$REMOVE"
]
}
}
}
])
The result:
{ }
{ "my_array" : [ { "a" : 1 } ] }
{ }
{ "a" : 1 }
{ }
{ "my_array" : [ { "a" : 2 }, { "a" : 3 } ] }
You can't do that in a query, however in an aggregations you can add $filter to you pipeline, like so:
db.collection.aggregate([
{
$project: {
my_array: {
$filter: {
input: "$my_array",
as: "elem",
cond: {
$ne: [
{},
"$$elem"
]
}
}
}
}
}
])
Mongo Playground
However unless this is "correct" behavior I suggest you clean up your database, it's much simpler to maintain "proper" structure than to update all your queries everywhere.
You can use this update to remove these objects:
db.collection.update({
"myarray": {}
},
[
{
"$set": {
"my_array": {
$filter: {
input: "$my_array",
as: "elem",
cond: {
$ne: [
{},
"$$elem"
]
}
}
}
}
},
],
{
"multi": false,
"upsert": false
})
Mongo Playground

How to group an array by same value from different collections in Mongo aggregation

I need to group at an array named location by the same coordinates field from both engagements and broadcastreports. Also if one coordinate exist in one but not on the other, It should be included anyway.
In that array I need to include a scans which is the number of engagements related to a campaign_ad_group and a impressions field which is the sum of broadcastreports.analytics.impressions related also to a campaign_ad_group.
I have the following aggregation:
.aggregate([
{
$match: {
'_id': ObjectId("60520e1d09ba3a769ab85f68"),
}
},
{
$project: {
'_id': 1,
'broadcasts_to': 1
}
},
{
$lookup: {
from: 'campaignadgroups',
localField: '_id',
foreignField: 'campaign_id',
as:'campaign_ad_group'
}
},
{
$unwind: '$campaign_ad_group'
},
{
$lookup: {
from: 'broadcastplans',
let: { 'campaign_ad_group_id': '$campaign_ad_group._id' },
as: 'broadcastplans',
pipeline: [
{
$match: {
$expr: {
$eq: ['$campaign_ad_group_id','$$campaign_ad_group_id' ]
}
}
},
{ $project: { '_id': 1 } }
]
}
},
// Above it's only the necessary data gathering to get the needed engagements and broadcastreports.
{
$lookup: {
from: 'engagements',
let: { 'broadcastplan_ids': '$broadcastplans._id' },
as: 'engagements',
pipeline: [
{
$match: {
$expr: {
$and: [
{ $in: ['$broadcast_plan_id', '$$broadcastplan_ids'] },
{ $gte: ['$reported_at', ISODate('2019-12-29T15:02:47.305Z')] },
{ $lte: ['$reported_at', ISODate('2021-12-29T15:02:47.305Z')] }
]
}
}
}
]
}
},
{
$unwind: '$engagements'
},
{
$group: {
'_id': {
'adgroup_id': '$campaign_ad_group._id',
'coordinates': '$engagements.location.coordinates',
},
'adgroup_name': {$first: '$campaign_ad_group.name'},
'scans': {$sum: 1}
}
},
{
$group: {
'_id': '$_id.adgroup_id',
'name': {$first: '$adgroup_name'},
'location': {
$push: {
'scans': '$scans',
'coordinates': '$_id.coordinates'
}
}
}
}
])
The output I get is the following:
{
"_id" : ObjectId("5fe9f89f603eaa22b3d374f5"),
"name" : "Sick name",
"location" : [
{
"scans" : 1.0,
"coordinates" : [
-8.63294964028813,
56.1520268933931
]
},
{
"scans" : 1.0,
"coordinates" : [
-9.63294964028813,
58.1520268933931
]
}
]
}
Now it comes the part that I'm stuck at. I need to look up for the collection broadcastreports and group by its coordinates with the engagements coordinates and include a field impressions which is the sum of broadcastreports.analytics.impressions.
By removing the group pipeline and lookup from the broadcastreports it will look like so:
.aggregate([
.
. // ABOVE IS THE SAME DATA GATHERING AS BEFORE
.
{
$lookup: {
from: 'engagements',
let: { 'broadcastplan_ids': '$broadcastplans._id' },
as: 'engagements',
pipeline: [
{
$match: {
$expr: {
$and: [
{ $in: ['$broadcast_plan_id', '$$broadcastplan_ids'] },
{ $gte: ['$reported_at', ISODate('2019-12-29T15:02:47.305Z')] },
{ $lte: ['$reported_at', ISODate('2021-12-29T15:02:47.305Z')] }
]
}
}
}
]
}
},
{
$lookup: {
from: 'broadcastreports',
let: { 'broadcastplan_ids': '$broadcastplans._id' },
as: 'broadcastreports',
pipeline: [
{
$match: {
$expr: {
$and: [
{ $in: ['$broadcast_plan_id', '$$broadcastplan_ids'] },
{ $gte: ['$reported_at', ISODate('2019-12-29T15:02:47.305Z')] },
{ $lte: ['$reported_at', ISODate('2021-12-29T15:02:47.305Z')] }
]
}
}
}
]
}
},
])
This gets me all the data I need which is something like:
{
_id: ObjectId("60520e1d09ba3a769ab85f68"),
campaign_ad_group: {
_id: ObjectId("5fc1215a039674c600c0f533"),
name: 'Sick name'
},
broadcastplans: [_ids],
engagements: [
{
_id,
coordinates: [-9.63294964028813,58.1520268933931],
reported_at: Date
},
{
_id,
coordinates: [-8.63294964028813,56.1520268933931],
reported_at: Date
}
],
broadcastreports: [
{
_id,
coordinates: [-9.63294964028813,58.1520268933931],
reported_at: Date,
analytics: {
impressions: 10
}
},
{
_id,
coordinates: [-5.20923334343244,40.4874833434234],
reported_at: Date,
analytics: {
impressions: 5
}
}
]
}
I need to group this in a way that I get the following output:
[
{
_id: campaign_ad_group._id,
name: campaign_ad_group.name,
location: [
{
scans: 1,
impressions: 10,
coordinates: [-9.63294964028813,58.1520268933931]
},
{
scans: 0, // This coordinate does not exist on the engagements so its 0 but needs to be
// included anyway.
impressions: 5,
coordinates: [-5.20923334343244,40.4874833434234]
},
{
scans: 1,
impressions: 0, // Same here
coordinates: [-8.63294964028813,56.1520268933931]
},
]
}
]
Use $unionWith.
Documentation: https://docs.mongodb.com/manual/reference/operator/aggregation/unionWith/
The solution was to make use of unionWith. I did it like so:
.aggregate([
{
$match: {
'_id': ObjectId("60520e1d09ba3a769ab85f68"),
}
},
{
$project: {
'_id': 1,
'broadcasts_to': 1
}
},
{
$lookup: {
from: 'campaignadgroups',
localField: '_id',
foreignField: 'campaign_id',
as:'campaign_ad_group'
}
},
{
$unwind: '$campaign_ad_group'
},
{
$lookup: {
from: 'broadcastplans',
let: { 'campaign_ad_group_id': '$campaign_ad_group._id' },
as: 'broadcastplans',
pipeline: [
{
$match: {
$expr: {
$eq: ['$campaign_ad_group_id','$$campaign_ad_group_id' ]
}
}
},
{ $project: { '_id': 1 } }
]
}
},
// Needed data gathering for the next lookups
{
$lookup: {
from: 'engagements',
let: { 'broadcastplan_ids': '$broadcastplans._id' },
as: 'locations',
pipeline: [
{
$match: {
$expr: {
$and: [
{ $in: ['$broadcast_plan_id', '$$broadcastplan_ids'] },
{ $gte: ['$reported_at', ISODate('2019-12-29T15:02:47.305Z')] },
{ $lte: ['$reported_at', ISODate('2021-12-29T15:02:47.305Z')] }
]
}
}
},
{
$group: {
'_id': '$location.coordinates',
'location': {$first:'$location'},
'scans': {$sum: 1}
}
},
{
$unionWith: {
coll: 'broadcastreports',
pipeline: [
{
$match: {
$expr: {
$and: [
{ $in: ['$broadcast_plan_id', '$$broadcastplan_ids'] },
{ $gte: ['$reported_at', ISODate('2019-12-29T15:02:47.305Z')] },
{ $lte: ['$reported_at', ISODate('2021-12-29T15:02:47.305Z')] }
]
}
}
},
]
}
},
{
$group: {
'_id': '$location.coordinates',
'impressions': {$sum: '$analytics.impressions'},
'scans': {$first: '$scans'}
}
},
{
$project: {
'_id': 0,
'coordinates': '$_id',
'impressions': {$ifNull: ['$impressions', 0]},
'scans': {$ifNull: ['$scans',0]}
}
}
]
}
},
{
$group: {
'_id': '$campaign_ad_group._id',
'name': {$first:'$campaign_ad_group.name'},
'locations': {$first: '$locations'}
}
}
])
The output:
{
"_id" : ObjectId("5fe9f89f603eaa22b3d374f5"),
"name" : "Sick name",
"locations" : [
{
"coordinates" : [
-9.63294964028813,
42.1520268933931
],
"impressions" : 0,
"scans" : 1.0
},
{
"coordinates" : [
-8.63294964028813,
41.1520268933931
],
"impressions" : 0,
"scans" : 1.0
},
{
"coordinates" : [
0,
0
],
"impressions" : 1,
"scans" : 0.0
}
]
}

Optimize multiple "and" statements in mongo aggregate

Is there a simpler way that would also improve the performance of this mongodb query. I know I am suppose to group the either one or the other but cant find any docs or example to help me out.
const facetQuery = { $facet: {
xCreated: [
{ $match : { $and : [{ queueStatus: 'Created' }, { queueType: 'x' } ]}},
{ $count: "Created" },
],
xApproved: [
{ $match : { $and : [{ queueStatus: 'Approved' }, { queueType: 'x' }]}},
{ $count: "Approved" }
],
xDisapproved: [
{ $match : { $and : [{ queueStatus: 'Disapproved' }, { queueType: 'x' }]}},
{ $count: "Disapproved" }
],
yCreated: [
{ $match : { $and : [{ queueStatus: 'Created' }, { queueType: 'y' }]}},
{ $count: "Created" },
],
yApproved: [
{ $match : { $and : [{ queueStatus: 'Approved' }, { queueType: 'y' }]}},
{ $count: "Approved" }
],
yDisapproved: [
{ $match : { $and : [{ queueStatus: 'Disapproved' }, { queueType: 'y' }]}},
{ $count: "Disapproved" }
],
zCreated: [
{ $match : { $and : [{ queueStatus: 'Created' }, { queueType: 'z' }]}},
{ $count: "Created" },
],
zApproved: [
{ $match : { $and : [{ queueStatus: 'Approved' }, { queueType: 'z' }]}},
{ $count: "Approved" }
],
zDisapproved: [
{ $match : { $and : [{ queueStatus: 'Disapproved' }, { queueType: 'z' }]}},
{ $count: "Disapproved" }
],
}};
Oh wow, instead of doing all these separate matches and count you can just dynamically $group on both status and type and then construct the object you need from that:
db.collection.aggregate([
{
$group: {
_id: {
type: "$queueType",
status: "$queueStatus"
},
ApprovedCount: {
$sum: {
$cond: [
{
$eq: [
"$queueStatus",
"Approved"
]
},
1,
0
]
}
},
CreatedCount: {
$sum: {
$cond: [
{
$eq: [
"$queueStatus",
"Created"
]
},
1,
0
]
}
},
DisapprovedCount: {
$sum: {
$cond: [
{
$eq: [
"$queueStatus",
"Disapproved"
]
},
1,
0
]
}
},
}
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: {
$arrayToObject: [
[
{
k: {
$concat: [
"$_id.type",
"$_id.status"
]
},
v: {
$switch: {
branches: [
{
case: {
$eq: [
"$_id.status",
"Approved"
]
},
then: "$ApprovedCount"
},
{
case: {
$eq: [
"$_id.status",
"Created"
]
},
then: "$CreatedCount"
},
{
case: {
$eq: [
"$_id.status",
"Disapproved"
]
},
then: "$DisapprovedCount"
},
]
}
}
}
]
]
}
}
}
}
])
Mongo Playground

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