How to transform the data using $if $else condition MongoDB
mongoPlayground
I am getting worst digging into $if $expr condition
This playground should return nothing because date is $gt then main collection but it does return me data.
So condition should say if history's date is $gt then main collection should return nothing else return the matched criteria data.
the first history record only is used, as it has the same _id as the history_id in the main collection
so if you check the first history document
{
"_id": ObjectId("5e4e755b380054797d9db627"),
"user_id": "5e4e74eb380054797d9db625",
"history_id": ObjectId("5e4e755b380054797d9db627"),
"date": 1587650683433,
"__v": 1
}
and your query
db.main.aggregate([
{
$lookup: {
from: "history",
localField: "history_id",
foreignField: "_id",
as: "History"
}
},
{
$unwind: "$History"
},
{
"$match": {
$expr: {
$cond: {
if: {
$eq: [
"5e4e74eb380054797d9db623",
"$History.user_id"
]
},
then: {
$and: [
{
$gt: [
"$date",
"$History.date"
]
},
{ // also, this part has no meaning as the same condition is used in the if part
$eq: [
"5e4e74eb380054797d9db623",
"$History.user_id"
]
}
]
},
else: {}
}
}
}
}
])
the if condition is not satisfied, as the user_id in history model "5e4e74eb380054797d9db625" does not equal to the specified id "5e4e74eb380054797d9db623"
so, it goes to the else part which is getting all the documents in the main collection
note: I've added some comment in the query, could you check it too?
If I am not wrong I guess this is what you're expecting :-
db.main.aggregate([
{
$lookup: {
from: "history",
localField: "history_id",
foreignField: "_id",
as: "History"
}
},
{
$unwind: "$History"
},
{
"$match": {
$expr: {
$and: [
{
$eq: [
"$_id",
"$History.user_id"
]
},
{
$gt: [
"$date",
"$Histoy.date"
]
}
]
}
}
}
])
Well, few modification might be needed to fulfill your need.
Related
How to check if a field is not existing inside lookup expression? The answers in similar questions are not helpful (e.g. {$eq : null} or $exists:true).
For example, I want to lookup inventory only if disabled is not existing.
db.orders.aggregate([
{
$lookup: {
from: "inventory",
let: {item: "$item"},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: ["$sku", "$$item" ]
},
{
$eq: [ "$disabled", null ]
}
]
}
}
},
],
as: "inv"
}
}
])
A playground sample is here
You can use $exists outside the $expr:
db.orders.aggregate([
{
$lookup: {
from: "inventory",
let: {item: "$item"},
pipeline: [
{
$match: {
$and: [
{$expr: {$eq: ["$sku", "$$item"]}},
{disabled: {$exists: false}}
]
}
}
],
as: "inv"
}
}
])
See how it works on the playground example
you could use:
{ $match: { someField: { $exists: true } } }
before the look up, to filter out the documents that you do not want to look up
I have a problem with MongoDB syntax.
I have two documents:
alley(the "tree" field is the ID of the tree):
{
"_id": {"$oid": "62572d82cc40164fef7f1a56"},
"name": "good alley",
"tree": [
{"$oid": "626976eb4b93122bc617d701"},
{"$oid": "626976eb4b93122bc617d702"}
]
},
.......
tree:
{
"_id": {"$oid": "626976eb4b93122bc617d701"},
"dateInstall": {"$date": "2021-02-27T00:00:00.000Z"},
"species": [
{"$oid": "62585a63edfc726a4ff24fb8"}
]
},
.......
I need to write a query "an alley where trees were not planted last year"
My Code
db.alley.aggregate([
{
$lookup: {
from: "tree",
localField: "tree",
foreignField: "_id",
as: "tree"
}
},
{
$match: {{$not:{$and:[
{"tree.dateInstall": {$gt: new ISODate("2020-12-31")}},
{"tree.dateInstall": {$lt: new ISODate("2022-01-01")}}
]
}}}
}
]);
You should first $unwind trees in alleys so you can properly $lookup the trees in tree collection. Use pipeline inside lookup to query only trees planted last year. Finally $group trees into alleys again and use $match to filter out those alleys without trees.
db.getCollection("alley").aggregate([
{
$unwind: "$tree",
},
{
$lookup: {
from: "tree",
let: { tree: "$tree" },
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: [ "$$tree", "$_id" ] },
{ $gt: [ "$dateInstall", new ISODate("2020-12-31") ] },
{ $lt: [ "$dateInstall", new ISODate("2022-01-01") ] },
]
}
}
}
],
localField: "tree",
foreignField: "_id",
as: "tree"
}
},
{
$group: {
_id: { id: "$_id", name: "$name" },
trees: { $addToSet: { $first: "$tree" } }
}
},
{
$match: {
trees: { $size: 0 }
}
}
])
I have a class model which has field ref.
I'm trying to fetch only records that match the condition in lookup.
so what i did:
{
$lookup: {
from: 'fields',
localField: "field",
foreignField: "_id",
as: 'FieldCollege',
},
},
{
$addFields: {
"FieldCollege": {
$arrayElemAt: [
{
$filter: {
input: "$FieldCollege",
as: "field",
cond: {
$eq: ["$$field.level", req.query.level]
}
}
}, 0
]
}
}
},
The above code works fine and returning the FieldCollege if the cond is matched.
but the thing is, i wanted to return the class records only if the FieldCollege is not empty.
I'm totally new to mongodb. so i tried something like this:
{
$match: {
'FieldCollege': { $exists: true, $ne: [] }
}
},
Obv this didn't work.
does mongodb support something like this or am i complicating things?
EDIT:
the result from the above code:
"Classes": [
{
"_id": "613245664c6ea614e001fcef",
"name": "test",
"language": "en",
"year_cost": "3232323",
"FieldCollege":[] // with $unwind
}
],
expected Result:
"Classes": [
// FieldCollege is empty
],
I think the good option is to use lookup with pipeline, and see the final version of your query,
$lookup with fields collection and match your both conditions
$limit to result one document
$match FieldCollege is not empty []
$addElemAt to get first element from result FieldCollege
[
{
$lookup: {
from: "fields",
let: { field: "$field" },
pipeline: [
{
$match: {
$and: [
{ $expr: { $eq: ["$$field", "$_id"] } },
{ level: req.query.level }
]
}
},
{ $limit: 1 }
],
as: "FieldCollege"
}
},
{ $match: { FieldCollege: { $ne: [] } } },
{
$addFields: {
FieldCollege: { $arrayElemAt: ["$FieldCollege", 0] }
}
}
]
See my condition does work fine for my history's first collection but for second it does not work either If I replace my specified id with 5e4a8d2d3952132a08ae5724 that exists in my second object of history's collection return me all data which is not correct because it's date is greater then main collection's date so it should return me nothing please suggest how to fix it.
db.main.aggregate([
{
$lookup: {
from: "history",
localField: "history_id",
foreignField: "history_id",
as: "History"
}
},
{
$unwind: "$History"
},
{
"$match": {
$expr: {
$cond: {
if: {
$eq: [
"5e4a8d2d3952132a08ae5764",
"$History.user_id"
]
},
then: {
$and: [
{
$gt: [
"$date",
"$History.date"
]
},
{
$eq: [
"5e4a8d2d3952132a08ae5764",
"$History.user_id"
]
}
]
},
else: {}
}
}
}
}
])
when I put two object into history collection it does not work either MongoPlayground
Here is working playground with single history object https://mongoplayground.net/p/hrNofrq1c3S
The reason, why your query (which is posted in the OP) is not working because, you have not specified anything in else part. So, the better approach will be with '$filter' operator.
You can try the below:
db.main.aggregate([
{
$lookup: {
from: "history",
localField: "history_id",
foreignField: "history_id",
as: "History"
}
},
{
$project: {
"History": {
$filter: {
input: "$History",
as: "his",
cond: {
$and: [
{
$lt: [
"$$his.date",
"$date"
]
},
{
$eq: [
"5e4a8d2d3952132a08ae5764",
"$$his.user_id"
]
}
]
}
}
},
data: 1,
history_id: 1,
sender_id: 1,
text: 1,
date: 1
}
},
{
$unwind: "$History"
}
])
MongoPlayGroundLink
This is my aggregation query
db.user.aggregate([
{ $addFields: { user_id: { $toString: "$_id" } } },
{
$lookup: {
from: "walletData",
let: { id: "$user_id" },
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: ["$userId", "$$id"]
},
{
$gt: ["$lastBalance", 0]
}
]
}
}
}
],
as: "balance"
}
}
])
I get the desired output from this result but need to join one more collection in this query. How can i achieve that?
For example consider these collections:
user : {
"_id": ObjectId("xyz")
}
walletData:{
"userId": "xyz",
"lastBalance": 5
}
AnotherWalletdata:{
"userId": "xyz",
"lastBalance": 6
}
I got the result after joining first two tables how do i join the third table only if the balance of the second table(walletData) is greater than zero?
Expected Output :
{"id":"xyz",
"walletdataBal":5,
"AnotherWalletDataBal":6
}
You can join any number of collections by using only $lookup and $unwind one after another followed by Conditional Projection for whatever that's required at last. Below is the well-tested and working solution for the same :
db.user.aggregate([
{$lookup: {from: "walletData", localField: "_id", foreignField: "userId", as: "walletDataBal"}},
{$unwind: "$walletDataBal"},
{$lookup: {from: "anotherwalletData", localField: "_id", foreignField: "userId", as: "anotherWalletDataBal"}},
{$unwind: "$anotherWalletDataBal"},
{$project: {"id": "$_id", "_id": 0, walletDataBal: "$walletDataBal.lastBalance",
anotherWalletDataBal: {$cond:
{if: { $gt: [ "$walletDataBal.lastBalance", 0 ] },
then: "$anotherWalletDataBal.lastBalance",
else: "$$REMOVE" }}}
]).pretty();
You can add another $lookup stage to achieve the output
db.user.aggregate([
{ "$addFields": { "user_id": { "$toString": "$_id" } } },
{ "$lookup": {
"from": "walletData",
"let": { "id": "$user_id" },
"pipeline": [
{ "$match": {
"$expr": {
"$and": [
{ "$eq": ["$userId", "$$id"] },
{ "$gt": ["$lastBalance", 0] }
]
}
}}
],
"as": "balance"
}},
{ "$lookup": {
"from": "anotherWalletData",
"let": { "id": "$user_id" },
"pipeline": [
{ "$match": {
"$expr": {
"$and": [
{ "$eq": ["$userId", "$$id"] },
{ "$gt": ["$lastBalance", 0] }
]
}
}}
],
"as": "anotherWalletData"
}},
{ "$project": {
"walletdataBal": { "$arrayElemAt": ["$balance.lastBalance", 0] },
"anotherwalletdataBal": {
"$arrayElemAt": ["$anotherWalletData.lastBalance", 0]
}
}}
])
{ $unwind: "$balance" },
{ $lookup: {
from: "walletData",
localField: "balance.userId",
foreignField: "userId",
as:"anotherwalletData"
}}
])
I solved my query, I had to apply unwind after the above lookup.