Match and add new field to each array elements in Mongodb aggregation - mongodb

I have two collections below and I'm creating a view table by using the aggregation and merge.
account table:
{
"_id": {
"$oid": "5f61b573db16185cebfeaaf9"
},
"status": "ACTIVE",
"personalInfo": {
"entityName": "John Doe",
...
},
"createdDateTime": {
"$date": "2020-09-16T06:49:23.103Z"
},
"modifiedDateTime": {
"$date": "2020-09-16T06:49:23.103Z"
}
}
account_transactions:
{
"_id": {
"$oid": "5f61b5b3db16185cebfeab0c"
},
"accountId": "5f61b573db16185cebfeaaf9",
"transactionType": "Payment",
"transactionStatus": "NEW",
"createdDate": {
"$date": "2020-09-16T06:50:27.305Z"
},
"createdBy": "sujal.shakya#joinsage.com",
"lastModifiedDate": {
"$date": "2020-09-16T10:23:06.617Z"
}
}
{
"_id": {
"$oid": "5f61b5b3db16185cebfeab0f"
},
"accountId": "5f61b573db16185cebfeaaf9",
"transactionType": "Credit",
"createdDate": {
"$date": "2020-09-16T06:50:27.305Z"
},
"createdBy": "sujal.shakya#joinsage.com",
"lastModifiedDate": {
"$date": "2020-09-16T10:23:06.617Z"
}
}
Below mongodb query creates a view table with each account having multiple transactions if exist and if no transactions exists then adds dummy transaction to with some dummy fields.
db.account.aggregate(
[
{
$match: {
'status': 'ACTIVE'
}
},
{$addFields: {"accountId": {"$toString": "$_id"}}},
{
$lookup: {
from: "account_transactions",
localField: "accountId",
foreignField: "accountId",
as: "transactions"
}
},
{
"$project": {
"accountType": 1,
"transactions": {
"$cond": [
{ "$eq": [ "$transactions", [] ] },
{$concatArrays: [[{"lastModifiedDate": {$arrayElemAt: ["$account.modifiedDateTime",0]}, "transactionType" : "BLANK"}]] },//adds dummy fields
{"$filter": {
"input": "$transactions",
"as": "transactions",
"cond": {
"$or": [
{
"$eq": [
"$$transactions.transactionType",
"Payment"
]
},
{
"$eq": [
"$$transactions.transactionType",
"Sale"
]
},
{
"$eq": [
"$$transactions.transactionType",
"Debit"
]
}
]
}
}
}
},
"createdBy": 1
}
},
{$merge: {into: "account_view_table", on: "_id", whenMatched: "replace", whenNotMatched: "insert"}}
])
What I need to do now is to add a new field in the view table is created above for each transactions by matching transaction type. For example: if an account has multiple transactions each with different transaction type, then check the transactionType of each transactions and add new fields "transactionTypeOrder" on each like:
I've been trying different things but nothing is working
{$addFields: {"transactions.transactionTypeOrder" :
{
$cond: [{$in:["$transactions.transactionType",["Payment"]]},"1",
{$cond: [ {$in:["$transactions.transactionType",["Sale"]]},"2",
{$cond: [{$in:["$transactions.transactionType",["Debit"]]},"3", "4" ]}
]}
]
}
}},
So if transactionType == "Payment" add transactionTypeOrder: 1, if transactionType == "Sales" add transactionTypeOrder: 2 etc. The output I'm looking for is something like below:
Case for 2nd Case checking another field also :
In the same transactions, I also have to do another check with different field to check if "transactionStatus" field exists and if not exists add "transactionStatusOrderAsc" field as "####" and "transactionStatusOrderDsc" as "~~~" and if exist same value as "transactionStatus" So the output will be as below:
So basically, I have to check transactionType and add new Filed and also at the same time check transactionStatus and two need fields.
//result view table
{
"_id": {
"$oid": "5f61b573db16185cebfeaafd"
},
//...other fields.
"transactions": [{
"transactionType": "Payment",
"transactionTypeOrder": 1,
"transactionStatus": "New",
"transactionStatusOrderAsc": "New",
"transactionStatusOrderDsc": "New"
},
{
"transactionType": "Sales",
"transactionTypeOrder": 2,
"transactionStatusOrderAsc": "####", //since transactionStatus field doesn't exist here
"transactionStatusOrderDsc": "~~~~"
},
{
"transactionType": "Debit",
"transactionTypeOrder": 3,
"transactionStatus": "Old",
"transactionStatusOrderAsc": "Old",
"transactionStatusOrderDsc": "Old"
}
]
}

You can use $map to apply to each element of your array, and $switch to check value and add your field.
db.account.aggregate([
{
$match: {
"status": "ACTIVE"
}
},
{
$addFields: {
"accountId": {
"$toString": "$_id"
}
}
},
{
$lookup: {
from: "account_transactions",
localField: "accountId",
foreignField: "accountId",
as: "transactions"
}
},
{
"$project": {
"accountType": 1,
"createdBy": 1,
transactions: /**adds dummy fields*/
{
"$map": {
"input": "$transactions",
"as": "transactions",
"in": {
$switch: {
branches: [
{
case: {
$eq: [
"$$transactions.transactionType",
"Payment"
]
},
then: {
transactionType: "$$transactions.transactionType",
transactionTypeOrder: 1
}
},
{
case: {
$eq: [
"$$transactions.transactionType",
"Sales"
]
},
then: {
transactionType: "$$transactions.transactionType",
transactionTypeOrder: 2
}
},
{
case: {
$eq: [
"$$transactions.transactionType",
"Debit"
]
},
then: {
transactionType: "$$transactions.transactionType",
transactionTypeOrder: 3
}
},
],
default: {
transactionType: "$$transactions.transactionType",
transactionTypeOrder: -1
}
}
}
}
}
}
},
])
If you need other field of each transaction, you have to add them in each case condition.
You can test it here
EDIT FROM FIRST COMMENT
If you have to include all your transactions fields, you can use $mergeObjects instead of manually creating your object.
db.account.aggregate([
{
$match: {
"status": "ACTIVE"
}
},
{
$addFields: {
"accountId": {
"$toString": "$_id"
}
}
},
{
$lookup: {
from: "account_transactions",
localField: "accountId",
foreignField: "accountId",
as: "transactions"
}
},
{
"$project": {
"accountType": 1,
"createdBy": 1,
transactions: /**adds dummy fields*/
{
"$map": {
"input": "$transactions",
"as": "transactions",
"in": {
$switch: {
branches: [
{
case: {
$eq: [
"$$transactions.transactionType",
"Payment"
]
},
then: {
$mergeObjects: [
"$$transactions",
{
transactionTypeOrder: 1
}
]
}
},
{
case: {
$eq: [
"$$transactions.transactionType",
"Sales"
]
},
then: {
$mergeObjects: [
"$$transactions",
{
transactionTypeOrder: 2
}
]
}
},
{
case: {
$eq: [
"$$transactions.transactionType",
"Debit"
]
},
then: {
$mergeObjects: [
"$$transactions",
{
transactionTypeOrder: 3
}
]
}
},
],
default: {
$mergeObjects: [
"$$transactions",
{
transactionTypeOrder: -1
}
]
}
}
}
}
}
}
},
])
Test it here

Related

Update more than one inner document in MongoDb

In my example project, I have employees under manager. Db schema is like this;
{
"employees": [
{
"name": "Adam",
"_id": "5ea36b27d7ae560845afb88e",
"bananas": "allowed"
},
{
"name": "Smith",
"_id": "5ea36b27d7ae560845afb88f",
"bananas": "not-allowed"
},
{
"name": "John",
"_id": "5ea36b27d7ae560845afb88g",
"bananas": "not-allowed"
},
{
"name": "Patrick",
"_id": "5ea36b27d7ae560845afb88h",
"bananas": "allowed"
}
]
}
In this case Adam is allowed to eat bananas and Smith is not. If I have to give the permission of eating bananas from Adam to Smith I need to perform update operation twice like this:
db.managers.update(
{ 'employees.name': 'Adam' },
{ $set: { 'employees.$.bananas': 'not-allowed' } }
);
and
db.managers.update(
{ 'employees.name': 'Smith' },
{ $set: { 'employees.$.bananas': 'allowed' } }
);
Is it possible to handle this in a single query?
You can use $map and $cond to perform conditional update to the array entries depending on the name of the employee. A $switch is used for potential extension of cases.
db.collection.update({},
[
{
"$set": {
"employees": {
"$map": {
"input": "$employees",
"as": "e",
"in": {
"$switch": {
"branches": [
{
"case": {
$eq: [
"$$e.name",
"Adam"
]
},
"then": {
"$mergeObjects": [
"$$e",
{
"bananas": "not-allowed"
}
]
}
},
{
"case": {
$eq: [
"$$e.name",
"Smith"
]
},
"then": {
"$mergeObjects": [
"$$e",
{
"bananas": "allowed"
}
]
}
}
],
default: "$$e"
}
}
}
}
}
}
])
Mongo Playground
db.managers.update(
{
$or: [
{"employees.name": "Adam"},
{"employees.name": "Smith"}
]
},
{
$set: {
"employees.$[e].bananas": {
$cond: [{ $eq: ["$e.name", "Adam"] }, "not-allowed", "allowed"]
}
}
},
{
arrayFilters: [{ "e.name": { $in: ["Adam", "Smith"] } }]
}
)

Mongodb Aggregation :- Get specific fields from $lookup nested array

I am trying to get specific fields from the array I got after aggregate, lookup and some cond
Below you can see my query
const attendanceData = await User.aggregate([
{
$match: {
lastLocationId: Mongoose.Types.ObjectId(typeId),
isActive: true,
},
},
{
$project: {
_id: 1,
workerId: 1,
workerFirstName: 1,
workerSurname: 1,
},
},
{
$lookup: {
from: "attendances",
localField: "_id",
foreignField: "employeeId",
as: "attendances",
},
},
{
$set: {
attendances: {
$filter: {
input: "$attendances",
cond: {
$and: [
{
$gte: ["$$this.Date", new Date(fromDate)],
},
{
$lte: ["$$this.Date", new Date(toDate)],
},
{
$eq: ["$$this.createdAs", dataType],
},
{
$eq: ["$$this.status", true],
},
{
$eq: ["$$this.workerType", workerType],
},
],
},
},
},
},
},
{ $skip: 0 },
{ $limit: 10 },
]);
The data as a response i get below
{
"attendanceSheet": [
{
"_id": "60dd77c14524e6c116e16aaa",
"workerFirstName": "FIRST NAME1",
"workerSurname": "SURNAME1",
"workerId": "1",
"attendances": [
{
"_id": "6130781085b5055a15c32f2u",
"workerId": "1",
"workerFullName": "FIRST NAME",
"workerType": "Employee",
"Date": "2022-10-01T00:00:00.000Z",
"createdAs": "ABSENT"
},
{
"_id": "6130781085b5055a15c32f2u",
"workerId": "1",
"workerFullName": "FIRST NAME",
"workerType": "Employee",
"Date": "2022-10-02T00:00:00.000Z",
"createdAs": "ABSENT"
}
]
},
{
"_id": "60dd77c24524e6c116e16c0f",
"workerFirstName": "FIRST NAME2",
"workerSurname": "Surname",
"workerId": "2",
"attendances": [
{
"_id": "6130781a85b5055a15c3455y",
"workerId": "2",
"workerFullName": "FIRST NAME2",
"workerType": "Employee",
"Date": "2022-10-02T00:00:00.000Z",
"createdAs": "ABSENT"
}
]
}
]
}
But I want data something like this below only few fields in not every fields
{
"attendanceSheet": [
{
"_id": "60dd77c14524e6c116e16aaa",
"workerFirstName": "FIRST NAME1",
"workerSurname": "Surname",
"workerId": "1",
"attendances": [
{
"_id": "6130781085b5055a15c32f2u",
"Date": "2022-10-01T00:00:00.000Z",
"createdAs": "ABSENT"
},
{
"_id": "6130781085b5055a15c32f2u",
"Date": "2022-10-02T00:00:00.000Z",
"createdAs": "ABSENT"
}
]
},
{
"_id": "60dd77c24524e6c116e16c0f",
"workerFirstName": "FIRST NAME2",
"workerSurname": "Surname",
"workerId": "2",
"attendances": [
{
"_id": "6130781a85b5055a15c3455y",
"Date": "2022-10-02T00:00:00.000Z",
"createdAs": "ABSENT"
}
]
}
]
}
You could simplify/refactor your aggregation pipeline by putting all the matching in a "$lookup" "pipeline".
db.users.aggregate([
{
"$match": {
"lastLocationId": ObjectId("0123456789abcdef01234567"),
"isActive": true
}
},
{
"$project": {
"workerId": 1,
"workerFirstName": 1,
"workerSurname": 1
}
},
{
"$lookup": {
"from": "attendances",
"localField": "_id",
"foreignField": "employeeId",
"as": "attendances",
// do all the matching here
"pipeline": [
{
"$match": {
"Date": {
// fromDate, toDate
"$gte": ISODate("2022-09-01T00:00:00Z"),
"$lte": ISODate("2022-09-30T23:59:59Z")
},
// dataType
"createdAs": "ABSENT",
"status": true,
// workerType
"workerType": "Employee"
}
},
{
"$project": {
"Date": 1,
"createdAs": 1
}
}
]
}
},
{$skip: 0},
{$limit: 10}
])
Try it on mongoplayground.net.
One option to get from what you have to the requested output is to $map and $reduce:
db.collection.aggregate([
{
$set: {
attendanceSheet: {
$map: {
input: "$attendanceSheet",
as: "external",
in: {
$mergeObjects: [
"$$external",
{
attendances: {
$reduce: {
input: "$$external.attendances",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[
{
_id: "$$this._id",
createdAs: "$$this.createdAs",
Date: "$$this.Date"
}
]
]
}
}
}
}
]
}
}
}
}
}
])
See how it works on the playground example
The below modification worked for me
const attendanceData = await User.aggregate([
{
$match: {
lastLocationId: Mongoose.Types.ObjectId(typeId),
isActive: true,
},
},
{
$project: {
_id: 1,
workerId: 1,
workerFirstName: 1,
workerSurname: 1,
},
},
{
$lookup: {
from: "attendances",
localField: "_id",
foreignField: "employeeId",
as: "attendances",
},
},
{
$set: {
attendances: {
$filter: {
input: "$attendances",
cond: {
$and: [
{
$gte: ["$$this.Date", new Date(fromDate)],
},
{
$lte: ["$$this.Date", new Date(toDate)],
},
{
$eq: ["$$this.createdAs", dataType],
},
{
$eq: ["$$this.status", true],
},
{
$eq: ["$$this.workerType", workerType],
},
],
},
},
},
},
},
{
$set: {
attendances: {
$reduce: {
input: "$attendances",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[
{
_id: "$$this._id",
createdAs: "$$this.createdAs",
Date: "$$this.Date",
},
],
],
},
},
},
},
},
{ $skip: 0 },
{ $limit: 10 },
]);

Filter documents that have id in another collection in MongoDB with aggregation framework

So I have two collection. collectionA and collectionB
collection A has following documents
db={
"collectiona": [
{
"_id": "6173ddf33ed09368a094e68a",
"title": "a"
},
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
],
"collectionb": [
{
"_id": "6173ddf33ed0wef368a094zq",
"collectionaID": "6173ddf33ed09368a094e68a",
"data": [
{
"userID": "123",
"visibility": false,
"response": false
},
{
"userID": "2345",
"visibility": true,
"response": true
}
]
},
{
"_id": "6173ddf33ed09368awef4e68g",
"collectionaID": "61wefdf33ed09368a094e6dc",
"data": [
{
"userID": "5678",
"visibility": false,
"response": false
},
{
"userID": "674",
"visibility": true,
"response": false
}
]
}
]
}
So What I need is documents from collection A which has response false in collection B
and document should be sorted by first the ones that have visibility false and then the ones that have visibility true
for eg. userID : 123 should get 3 documents
{
"_id": "6173ddf33ed09368a094e68a",
"title": "a"
},
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
whereas userID 2345 should get two
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
User 674 will receive 3 objects from collection A but second would be in the last as it has visibility true for that document
{
"_id": "6173ddf33ed09368a094e68a",
"title": "a"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
MongoDB Playground link : https://mongoplayground.net/p/3rLry0FPlw-
Really appreciate the help. Thanks
You can start from collectionA:
$lookup the collectionB for the record related to the user specified
filter out collectionB documents according to response
assign a helper sortrank field based on the visibility and whether collectionaID is a match
$sort according to sortrank
wrangle back to the raw collection A
db.collectiona.aggregate([
{
"$lookup": {
"from": "collectionb",
let: {
aid: "$_id"
},
"pipeline": [
{
$unwind: "$data"
},
{
$match: {
$expr: {
$and: [
{
$eq: [
"$data.userID",
"2345"
]
},
{
$eq: [
"$collectionaID",
"$$aid"
]
}
]
}
}
}
],
"as": "collB"
}
},
{
$match: {
"collB.data.response": {
$ne: true
}
}
},
{
"$unwind": {
path: "$collB",
preserveNullAndEmptyArrays: true
}
},
{
"$addFields": {
"sortrank": {
"$cond": {
"if": {
$eq: [
"$collB.data.visibility",
false
]
},
"then": 1,
"else": {
"$cond": {
"if": {
$eq: [
"$collB.collectionaID",
"$_id"
]
},
"then": 3,
"else": 2
}
}
}
}
}
},
{
$sort: {
sortrank: 1
}
},
{
$project: {
collB: false,
sortrank: false
}
}
])
Here is the Mongo playground for your reference.

How to combine mongodb original output of query with some new fields?

Collection:
[
{
"name": "device1",
"type": "a",
"para": {
"number": 3
}
},
{
"name": "device2",
"type": "b",
"additional": "c",
"para": {
"number": 1
}
}
]
My query:
db.collection.aggregate([
{
"$addFields": {
"arrayofkeyvalue": {
"$objectToArray": "$$ROOT"
}
}
},
{
"$unwind": "$arrayofkeyvalue"
},
{
"$group": {
"_id": null,
"allkeys": {
"$addToSet": "$arrayofkeyvalue.k"
}
}
}
])
The output currently:
[
{
"_id": null,
"allkeys": [
"additional",
"_id",
"para",
"type",
"name"
]
}
]
Detail see Playground
What I want to do is add a new column which includes all of top key of the mongodb query output, exclude "para". And then combine it with the old collection to form a new json.
Is it possible?
The expected result:
{
"column": [{"prop": "name"}, {"prop": "type"}, {"prop": "additional"}],
"columnData": [
{
"name": "device1",
"type": "a",
"para": {
"number": 3
}
},
{
"name": "device2",
"type": "b",
"additional": "c",
"para": {
"number": 1
}
}
]
}
You have the right general idea in mind, here's how I would do it by utilizing operators like $filter, $map and $reduce to manipulate the objects structure.
I separated the aggregation into 3 parts for readability but you can just merge stage 2 and 3 if you wish.
db.collection.aggregate([
{
"$group": {
"_id": null,
columnData: {
$push: "$$ROOT"
},
"keys": {
"$push": {
$map: {
input: {
"$objectToArray": "$$ROOT"
},
as: "field",
in: "$$field.k"
}
}
}
}
},
{
"$addFields": {
unionedKeys: {
$filter: {
input: {
$reduce: {
input: "$keys",
initialValue: [],
in: {
"$setUnion": [
"$$this",
"$$value"
]
}
}
},
as: "item",
cond: {
$not: {
"$setIsSubset": [
[
"$$item"
],
[
"_id",
"para"
]
]
}
}
}
}
}
},
{
$project: {
_id: 0,
columnData: 1,
column: {
$map: {
input: "$unionedKeys",
as: "key",
in: {
prop: "$$key"
}
}
}
}
}
])
Mongo Playground

MongoDB Aggregation - Lookup pipeline not returning any documents

I'm having hard time getting $lookup with a pipeline to work in MongoDB Compass.
I have the following collections:
Toys
Data
[
{
"_id": {
"$oid": "5d233c3bb173a546386c59bb"
},
"type": "multiple",
"tags": [
""
],
"searchFields": [
"Jungle Stampers - Two",
""
],
"items": [
{
"$oid": "5d233c3cb173a546386c59bd"
},
{
"$oid": "5d233c3cb173a546386c59be"
},
{
"$oid": "5d233c3cb173a546386c59bf"
},
{
"$oid": "5d233c3cb173a546386c59c0"
},
{
"$oid": "5d233c3cb173a546386c59c1"
},
{
"$oid": "5d233c3cb173a546386c59c2"
},
{
"$oid": "5d233c3cb173a546386c59c3"
},
{
"$oid": "5d233c3cb173a546386c59c4"
}
],
"name": "Jungle Stampers - Two",
"description": "",
"status": "active",
"category": {
"$oid": "5cfe727cac920000086b880e"
},
"subCategory": "Stamp Sets",
"make": "",
"defaultCharge": null,
"defaultOverdue": null,
"sizeCategory": {
"$oid": "5d0cfde57561e107c88fbde3"
},
"ageFrom": {
"$numberInt": "24"
},
"ageTo": {
"$numberInt": "120"
},
"images": [
{
"_id": {
"$oid": "5d233c3bb173a546386c59bc"
},
"id": {
"$oid": "5d233c39b173a546386c59ba"
},
"url": "/toyimages/5d233c39b173a546386c59ba.jpg",
"thumbUrl": "/toyimages/thumbs/tn_5d233c39b173a546386c59ba.jpg"
}
],
"__v": {
"$numberInt": "2"
}
}
]
Loans
Data
[
{
"_id": {
"$oid": "5e1f1661b712215978c746d9"
},
"tags": [],
"member": {
"$oid": "5e17495e4f81ab3f900dbb63"
},
"source": "admin portal - potter1#gmail.com",
"items": [
{
"id": {
"$oid": "5e1f160eb712215978c746d5"
},
"status": "new",
"_id": {
"$oid": "5e1f1661b712215978c746db"
},
"toy": {
"$oid": "5d233c3bb173a546386c59bb"
},
"cost": {
"$numberInt": "0"
}
},
{
"id": {
"$oid": "5e1f160eb712215978c746d5"
},
"status": "new",
"_id": {
"$oid": "5e1f1661b712215978c746da"
},
"toy": {
"$oid": "5d233b1ab173a546386c59b5"
},
"cost": {
"$numberInt": "0"
}
}
],
"dateEntered": {
"$date": {
"$numberLong": "1579095632870"
}
},
"dateDue": {
"$date": {
"$numberLong": "1579651200000"
}
},
"__v": {
"$numberInt": "0"
}
}
]
I am trying to return a list of toys and their associated loans that have a status of 'new' or 'out'.
I can use the following $lookup aggregate to fetch all loans:
{
from: 'loans',
localField: '_id',
foreignField: 'items.toy',
as: 'loansSimple'
}
However I am trying to use a pipeline to load loans that have the two statuses I am interested in, but it always only returns zero documents:
{
from: 'loans',
let: {
'toyid': '$_id'
},
pipeline: [
{
$match: {
$expr: {
$and: [
{$eq: ['$items.toy', '$$toyid']},
{$eq: ['$items.status', 'new']} // changed from $in to $eq for simplicity
]
}
}
}
],
as: 'loans'
}
This always seems to return 0 documents, however I arrange it:
Have I made a mistake somewhere?
I'm using MongoDB Atlas, v4.2.2, MongoDB Compass v 1.20.4
You are trying to search $$toyid inside inner array, but Operator Expression $eq cannot resolve it.
Best solution: $let (returns filtered loans by criteria) + $filter (applies filter for inner array) operator helps us to get desired result.
db.toys.aggregate([
{
$lookup: {
from: "loans",
let: {
"toyid": "$_id",
"toystatus": "new"
},
pipeline: [
{
$match: {
$expr: {
$gt: [
{
$size: {
$let: {
vars: {
item: {
$filter: {
input: "$items",
as: "tmp",
cond: {
$and: [
{
$eq: [
"$$tmp.toy",
"$$toyid"
]
},
{
$eq: [
"$$tmp.status",
"$$toystatus"
]
}
]
}
}
}
},
in: "$$item"
}
}
},
0
]
}
}
}
],
as: "loans"
}
}
])
MongoPlayground
Alternative solution 1. Use $unwind to flatten items attribute. (We create extra field named tmp which stores items value, flatten it with $unwind operator, match as you were doing and then exclude from result)
db.toys.aggregate([
{
$lookup: {
from: "loans",
let: {
"toyid": "$_id"
},
pipeline: [
{
$addFields: {
tmp: "$items"
}
},
{
$unwind: "$tmp"
},
{
$match: {
$expr: {
$and: [
{
$eq: [
"$tmp.toy",
"$$toyid"
]
},
{
$eq: [
"$tmp.status",
"new"
]
}
]
}
}
},
{
$project: {
tmp: 0
}
}
],
as: "loans"
}
}
])
MongoPlayground
Alternative solution 2. We use $reduce to create toy's array and with $in operator we check if toyid exists inside this array.
db.toys.aggregate([
{
$lookup: {
from: "loans",
let: {
"toyid": "$_id"
},
pipeline: [
{
$addFields: {
toys: {
$reduce: {
input: "$items",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[
"$$this.toy"
]
]
}
}
}
}
},
{
$match: {
$expr: {
$in: [
"$$toyid",
"$toys"
]
}
}
},
{
$project: {
toys: 0
}
}
],
as: "loans"
}
}
])
$expr receives aggregation expressions, At that point $$items.toy is parsed for each element in an array as you would expect (however if it would it will still give you "bad" results as you'll get loans that have the required toy id and any other item with status new in their items array).
So you have two options to work around this:
If you don't care about the other items in the lookup'd document you can add an $unwind stage at the start of the lookup pipeline like so:
{
from: 'loans',
let: {
'toyid': '$_id'
},
pipeline: [
{
$unwind: "$items"
},
{
$match: {
$expr: {
$and: [
{$eq: ['$items.toy', '$$toyid']},
{$eq: ['$items.status', 'new']} // changed from $in to $eq for simplicity
]
}
}
}
],
as: 'loans'
}
If you do care about them just iterate the array in one of the possible ways to get a 'correct' match, here is an example using $filter
{
from: 'loads',
let: {
'toyid': '$_id'
},
pipeline: [
{
$addFields: {
temp: {
$filter: {
input: "$items",
as: "item",
cond: {
$and: [
{$eq: ["$$item.toy", "$$toyid"]},
{$eq: ["$$item.status", "new"]}
]
}
}
}
}
}, {$match: {"temp.0": {exists: true}}}
],
as: 'loans'
}