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 }
}
}
])
Related
I have collection like below named as "FormData",
{
"_id": ObjectId("5e3c27bf1ef77236945ef07b"),
"eed12747-0923-4290-b09c-5a05107f5609": "20200206",
"bd637691-782d-4cfd-8624-feeedfe11b3e": "20200206_1#mail.com"
}
I have another collection named as "Form" which will have Title of Fields,
{
"_id": ObjectId("5e3c27bf1ef77236945ef07b"),
"Fields":[
{
"FieldID": "eed12747-0923-4290-b09c-5a05107f5609",
"Title": "Phone"
},
{
"FieldID": "bd637691-782d-4cfd-8624-feeedfe11b3e",
"Title": "Email"
}]
}
Now I have to map element name with Form field title and I need result like below,
{
"_id": ObjectId("5e3c27bf1ef77236945ef07b"),
"Phone": "20200206",
"Email": "20200206_1#mail.com"
}
Please help me to solve this.
Thanks in advance!
You can:
$objectToArray to convert the $$ROOT document into an array of k-v pairs for future lookups
use a sub-pipeline in $lookup to find the value by the uuid
use $mergeObject to combine the original values(i.e. "20200206"...) with the new field name looked up (i.e. "Phone"...)
wrangle the result back into original form using $arrayToObject and $replaceRoot
db.FormData.aggregate([
{
$match: {
"_id": ObjectId("5e3c27bf1ef77236945ef07b")
}
},
{
$project: {
kv: {
"$objectToArray": "$$ROOT"
}
}
},
{
$unwind: "$kv"
},
{
"$lookup": {
"from": "Form",
"let": {
uuid: "$kv.k"
},
"pipeline": [
{
$match: {
"_id": ObjectId("5e3c27bf1ef77236945ef07b")
}
},
{
"$unwind": "$Fields"
},
{
$match: {
$expr: {
$eq: [
"$$uuid",
"$Fields.FieldID"
]
}
}
},
{
$project: {
_id: false,
k: "$Fields.Title"
}
}
],
"as": "formLookup"
}
},
{
$unwind: "$formLookup"
},
{
$project: {
kv: {
"$mergeObjects": [
"$kv",
"$formLookup"
]
}
}
},
{
$group: {
_id: "$_id",
kv: {
$push: "$kv"
}
}
},
{
"$project": {
newDoc: {
"$arrayToObject": "$kv"
}
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [
{
"_id": "$_id"
},
"$newDoc"
]
}
}
}
])
Mongo Playground
Another option is to start from Form collection and avoid $unwind:
$match and $lookup to get all needed data into one document
$objectToArray to get known keys for FormData
Match the items using $indexOfArray and $arrayElemAt and merge them using $mergeObjects. Then use arrayToObject to format the response
db.Form.aggregate([
{$match: {_id: ObjectId("5e3c27bf1ef77236945ef07b")}},
{$lookup: {
from: "FormData",
localField: "_id",
foreignField: "_id",
as: "formLookup",
pipeline: [{$project: {_id: 0}}]
}},
{$set: {formLookup: {$objectToArray: {$first: "$formLookup"}}}},
{$replaceRoot: {
newRoot: {
$mergeObjects: [
{$arrayToObject: {
$map: {
input: "$formLookup",
in: {$mergeObjects: [
{v: "$$this.v"},
{k: {$getField: {
input: {$arrayElemAt: [
"$Fields",
{$indexOfArray: ["$Fields.FieldID", "$$this.k"]}
]},
field: "Title"
}}}
]}
}
}},
{_id: "$_id"}
]
}
}}
])
See how it works on the playground example
I have this BD :
db={
"dashboard": [
{
"_id": "dashboard1",
"name": "test",
"user": 1
}
],
"templatefolders": [
{
"dashboardId": "dashboard1",
"folderId": "folder123",
"name": "folder",
"region": "XXX"
},
{
"dashboardId": "dashboard1",
"folderId": "folder123",
"name": "folder1",
"region": "YYY"
}
],
"folders": [
{
"_id": "folder123",
"name": "test1"
}
]
}
I have this query :
db.dashboard.aggregate([
{
$lookup: {
from: "templatefolders",
let: {
"boardId": "$_id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$dashboardId",
"$$boardId"
]
}
}
},
{
$lookup: {
from: "folders",
let: {
"folderId": "$folderId"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$_id",
"$$folderId"
]
}
}
},
],
as: "joinFolder"
},
},
],
as: "joinDashboard"
}
}
])
I want to fetch all dashbords where dashbord.templateFolder.name=="folder" OR dashbord.templateFolder.folders.name=="test1"
Please, how i can achieve this ?
How i can add many conditions in the nested pipeline lookup, where one condition is related to another condition.
One option is use $lookup and then $match.
In your case:
db.templatefolders.aggregate([
{$lookup: {
from: "folders",
let: {"folderId": "$folderId"},
pipeline: [
{$match: {
$expr: {$and: [
{$eq: ["$_id", "$$folderId"]},
{$eq: ["$name", "test1"]}
]}
}}
],
as: "folder"
}
},
{$match: {$or: [{"folder.0": {$exists: true}}, {name: "folder"}]}},
{$lookup: {
from: "dashboard",
as: "dashboard",
localField: "dashboardId",
foreignField: "_id"
}},
{$project: {_id: 0, dashboard: {$first: "$dashboard"}}},
{$replaceRoot: {newRoot: "$dashboard"}}
])
See how it works on the playground example
You did not define your requested result, but you can use group if you have duplicates and you want to remove them.
In below example, looking for new partner suggestions for user abc. abc has already sent a request to 123 so that can be ignored. rrr has sent request to abc but rrr is in the fromUser field so rrr is still a valid row to be shown as suggestion to abc
I have two collections:
User collection
[
{
_id: "abc",
name: "abc",
group: 1
},
{
_id: "xyz",
name: "xyyy",
group: 1
},
{
_id: "123",
name: "yyy",
group: 1
},
{
_id: "rrr",
name: "tttt",
group: 1
},
{
_id: "eee",
name: "uuu",
group: 1
}
]
Partnership collection (if users have already partnered)
[
{
_id: "abc_123",
fromUser: "abc",
toUser: "123"
},
{
_id: "rrr_abc",
fromUser: "rrr",
toUser: "abc"
},
{
_id: "xyz_rrr",
fromUser: "xyz",
toUser: "rrr"
}
]
My query below excludes the user rrr but it should not because its not listed in toUser field in the partnership collection corresponding to the user abc.
How to modify this query to include user rrr in this case?
db.users.aggregate([
{
$match: {
group: 1,
_id: {
$ne: "abc"
}
}
},
{
$lookup: {
from: "partnership",
let: {
userId: "$_id"
},
as: "prob",
pipeline: [
{
$set: {
users: [
"$fromUser",
"$toUser"
],
u: "$$userId"
}
},
{
$match: {
$expr: {
$and: [
{
$in: [
"$$userId",
"$users"
]
},
{
$in: [
"abc",
"$users"
]
}
]
}
}
}
]
}
},
{
$match: {
"prob.0": {
$exists: false
}
}
},
{
$sample: {
size: 1
}
},
{
$unset: "prob"
}
])
https://mongoplayground.net/p/utGMeHFRGmt
Your current query does not allow creating an existing connection regardless of the connection direction. If the order of the connection is important use:
db.users.aggregate([
{$match: {
group: 1,
_id: {$ne: "abc"}
}
},
{$lookup: {
from: "partnership",
let: { userId: {$concat: ["abc", "_", "$_id"]}},
as: "prob",
pipeline: [{$match: {$expr: {$eq: ["$_id", "$$userId"]}}}]
}
},
{$match: {"prob.0": {$exists: false}}},
{$sample: {size: 1}},
{$unset: "prob"}
])
See how it works on the playground example
For MongoDB 5 and later, I'd propose the following aggregation pipeline:
db.users.aggregate([
{
$match: {
group: 1,
_id: {
$ne: "abc"
}
}
},
{
$lookup: {
from: "partnership",
as: "prob",
localField: "_id",
foreignField: "toUser",
pipeline: [
{
$match: {
fromUser: "abc",
}
}
]
}
},
{
$match: {
"prob.0": {
$exists: false
}
}
},
{
$unset: "prob"
}
])
The following documents are returned (full result without the $sample stage):
[
{
"_id": "eee",
"group": 1,
"name": "uuu"
},
{
"_id": "rrr",
"group": 1,
"name": "tttt"
},
{
"_id": "xyz",
"group": 1,
"name": "xyyy"
}
]
The main difference is that the lookup connects the collections by the toUser field (see localField, foreignField) and uses a minimal pipeline to restrict the results further to only retrieve the requests from the current user document to "abc".
See this playground to test.
When using MongoDB < 5, you cannot use localField and foreignField to run the pipeline only on a subset of the documents in the * from*
collection. To overcome this, you can use this aggregation pipeline:
db.users.aggregate([
{
$match: {
group: 1,
_id: {
$ne: "abc"
}
}
},
{
$lookup: {
from: "partnership",
as: "prob",
let: {
userId: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$fromUser",
"abc"
]
},
{
$eq: [
"$toUser",
"$$userId"
]
}
]
}
}
}
]
}
},
{
$match: {
"prob.0": {
$exists: false
}
}
},
{
$unset: "prob"
}
])
The results are the same as for the upper pipeline.
See this playground to test.
For another, another way, this query starts from the partnership collection, finds which users to exclude, and then does a "$lookup" for everybody else. The remainder is just output formatting, although it looks like you may want to add a "$sample" stage at the end.
db.partnership.aggregate([
{
"$match": {
"fromUser": "abc"
}
},
{
"$group": {
"_id": null,
"exclude": {"$push": "$toUser" }
}
},
{
"$lookup": {
"from": "users",
"let": {
"exclude": {"$concatArrays": [["abc"], "$exclude"]
}
},
"pipeline": [
{
"$match": {
"$expr": {
"$not": {"$in": ["$_id", "$$exclude"]}
}
}
}
],
"as": "output"
}
},
{
"$project": {
"_id": 0,
"output": 1
}
},
{"$unwind": "$output"},
{"$replaceWith": "$output"}
])
Try it on mongoplayground.net.
I have two collections in MongoDB: "carts" and another "products"
Carts:
[{
"_id": {
"$oid": "62af0fefebc0b42a875c7df1"
},
"uuid": "6ca05ae0-522a-4db3-b380-2d2330ee1e27",
"cartitems": [
"62a0b24680cc2891148daf7b",
"62a7339d91d01868921afa0a",
"62a72f7191d01868921afa08",
"62a7330291d01868921afa09"
],
"created": "2022-06-19T14:00:47.846958537+02:00[Europe/Brussels]",
"lastupdate": "2022-06-19T14:01:06.15165564+02:00[Europe/Brussels]"
},
{...},
{...}]
products:
[{
"_id": {
"$oid": "62a0b24680cc2891148daf7b"
},
"name": "Product1",
"created": "2022-06-11T09:41:54.461308647+02:00[Europe/Brussels]",
"category": "Workshops",
"pricein": "28900",
"lastupdate": "2022-06-17T16:09:53.385655474+02:00[Europe/Brussels]"
},
{...},
{...}]
I would like to use Aggregate:
db.carts.aggregate([
{ $match: { uuid: "6ca05ae0-522a-4db3-b380-2d2330ee1e27" } },
{
$lookup: {
from: "products",
localField: "cartitems",
foreignField: "_id",
as: "output"
}
}
])
This is not working because localField: "cartitems" should be converted:
"$addFields": {
"convertedIdStr": {
"$toString": "$cartitems"
}
But I don't manage to convert because cartitems is an array.
Any help would be great, Thanks a lot!
Use $lookup with pipeline. In $lookup pipeline stage, add $match stage by filtering the (converted to string) product's _id is in cartitems (array) variable.
db.carts.aggregate([
{
$match: {
uuid: "6ca05ae0-522a-4db3-b380-2d2330ee1e27"
}
},
{
$lookup: {
from: "products",
let: {
cartitems: "$cartitems"
},
pipeline: [
{
$match: {
$expr: {
$in: [
{
$toString: "$_id"
},
"$$cartitems"
]
}
}
}
],
as: "output"
}
}
])
Sample Mongo Playground
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.