I am using mongodb atlas, I have buid one value. these is the users collection sample document details.
{"_id":{"$oid":"5f7de1f7e0044c8262f6adbc"},"bio":"Hi, I am a professional software developer, I have strong knowledge on coding.","email":"ats4#gmail.com","displayName":"ats demo","following":[],"name":"ats4demo6880904256665157633","image":" ","status":"active","isPrivate":false}
I tried to display all document except one.
"{\"collection\":\"users\",\"stages\":[{\"$match\":{\"$and\":[{\"_id\":{\"$oid\":\"%%args.user\"}},{\"following\":{\"$size\":0}}]}},{\"$lookup\":{\"from\":\"users\",\"localField\":\"status\",\"foreignField\":\"status\",\"as\":\"test\"}},{\"$unwind\":\"$test\"},{\"$project\":{\"test._id\":1,\"test.name\":1,\"test.displayName\":1,\"test.image\":1}}]}"
I have displayed all valuee except test._id: ObjectId("5f7f193585d5f70c177f6d27") at the project aggregate. but I can't.
can you please help me out with the solution. thanks in advance.
Took me a while to understand the question. I think this is what you are looking for.
db.collection.aggregate([
{
"$match": {
"$and": [
{
"_id": {
"$oid": "5f7de1f7e0044c8262f6adbc"
}
},
{
"following": {
"$size": 0
}
}
]
}
},
{
"$lookup": {
"from": "collection",
"localField": "status",
"foreignField": "status",
"as": "test"
}
},
{
"$unwind": "$test"
},
{
"$match": {
"test._id": {
$not: {
$eq: ObjectId("5f7de1f7e0044c8262f6adcc")
}
}
}
},
{
"$project": {
"test._id": 1,
"test.name": 1,
"test.displayName": 1,
"test.image": 1
}
}
])
Play around with the query here
Related
This is my Product Schema
const product = mongoose.Schema({
name: {
type: String,
},
category:{
type: ObjectId,
ref: Category
}
}
I want to return the product based on filters coming from the front end.
For example: Lets consider there are 2 categories Summer and Winter. When the user wants to filter product by Summer Category an api call would me made to http://localhost:8000/api/products?category=summer
Now my question is, how do I filter because from the frontend I am getting category name and there is ObjectId in Product Schema.
My attempt:
Project.find({category:categoryName})
If I've understood correctly you can try one of these queries:
First one is using pipeline into $lookup to match by category and name in one stage like this:
yourModel.aggregate([
{
"$lookup": {
"from": "category",
"let": {
"category": "$category",
},
"pipeline": [
{
"$match": {
"$expr": {
"$and": [
{
"$eq": [
"$id",
"$$category"
]
},
{
"$eq": [
"$name",
"Summer"
]
}
]
}
}
}
],
"as": "categories"
}
},
{
"$match": {
"categories": {
"$ne": []
}
}
}
])
Example here
Other way is using normal $lookup and then $filter the array returned by join.
yourModel.aggregate([
{
"$lookup": {
"from": "category",
"localField": "category",
"foreignField": "id",
"as": "categories"
}
},
{
"$match": {
"categories": {
"$ne": []
}
}
},
{
"$set": {
"categories": {
"$filter": {
"input": "$categories",
"as": "c",
"cond": {
"$eq": [
"$$c.name",
"Summer"
]
}
}
}
}
}
])
Example here
Note how both queries uses $match: { categories: { $ne : []}}, this is because if the lookup doesn't find any result it returns an empty array. By the way, this stage is optional.
Also you can add $unwind or $arrayElemAt index 0 or whatever to get only one value from the array.
Also, to do it in mongoose you only need to replace "Summer" with your variable.
I want to join two collections and find the documents where has one equal field and one unequal field!
This is what I was tried, But not work
db.collectionOne.aggregate[
{
"$match": {
"$and": [
{ "$text": { "$search": "this is my query" } },
{ "b": { "$eq": "60e849054d2f0d409041b6a2" } }
]
}
},
{ "$addFields": { "pID": { "$toString": "$_id" }, "score": { "$meta": "textScore" } } },
{
"$lookup": {
"from": "collectionsTwo",
"as": "collectionsTwoName",
"pipeline": [{
"$match": {
"$expr": {
"$and": [{
"$ne": ["$fieldOne", "60dd0f98d10f072e2a225502"] // This one is unqual field
}, { "$eq": ["$pID", "$fieldTwo"] }] // This one is equal field
}
}
}]
}
},
{ "$sort": { "score": -1 } },
{ "$limit": 1 }
])
Fields in the source document, i.e. $pID are not available inside the lookup pipeline.
In order to reference those values, you would need to define a variable using let, such as:
{
"$lookup": {
"from": "collectionsTwo",
"as": "collectionsTwoName",
"let": { "srcpID":"$pID" },
"pipeline": [{
"$match": {
"$expr": {
"$and": [{
"$ne": ["$fieldOne", "60dd0f98d10f072e2a225502"] // This one is unqual field
}, { "$eq": ["$$srcpID", "$fieldTwo"] }] // This one is equal field
}
}
}]
}
},
See https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#join-conditions-and-uncorrelated-sub-queries
Tried running a query on the aggregate function on Mongo which is currently taking 16 seconds when the result i wished for was under a second
{
"$lookup": {
"from": "session_attendances",
"let": { "id": "$_id" },
"pipeline": [
{
"$match": {
"$expr": {
"$eq": ["$student", "$$id"]
}
}
},
{
"$project": {
"attendance_code": "$attendance_code"
}
}
],
"as": "attendance"
}
},
{
// keep only matched students, can skip this and modifiy the next phase incase no such documents exists.
"$unwind": "$attendance"
},
{
"$lookup": {
"from": "attendance_codes",
"let": { "attendance_code": "$attendance.attendance_code" },
"pipeline": [
{
"$project": {
"type": 1
}
},
{
"$match": {
"$expr": {
"$eq": ["$_id", "$$attendance_code"]
}
}
}
],
"as": "attendance_code"
}
},
{
//again assuming we want to keep matched docs otherwise why lookup?
"$unwind": "$attendance_code"
},
{
"$group": {
"_id": { "a": "$attendance.attendance_code", "id": "$_id" },
"total": { "$sum": 1 },
"data": { "$first": "$$ROOT" } // if u want to keep document data
}
}
Hoping that some one can give me an answer to which part of my code is making the run time so slow.
Its not clear what your end goal is, if you wish to clarify that it would help me give an alternative to your current aggregation
With that said the second lookup stage is "useless" as you group right after without using any of the data gained by it, removing it will still get you the exact same result and save some time.
Assuming the second lookup is needed for some reason i recommend not nesting it but rather use after the first one, like so:
{
$lookup: {
from: 'session_attendances',
let: { 'id': '$_id' },
pipeline: [
{
"$match": {
"$expr": {
"$eq": ["$student", "$$id"]
}
}
}
,{
$project: {
attendance_code: '$attendance_code'
}
}
],
as: 'attendance'
}
},
{// keep only matched students, can skip this and modifiy the next phase incase no such documents exists.
$unwind: "$attendance"
},
{
$lookup: {
from: 'attendance_codes',
let: { 'attendance_code': '$attendance.attendance_code' },
pipeline: [
{
$project: {
type: 1
}
},
{
"$match": {
"$expr": {
"$eq": ["$_id", "$$attendance_code"]
}
}],
as: 'attendance_code'
}
}
},
{ //again assuming we want to keep matched docs otherwise why lookup?
$unwind: "$attendance_code"
},
{
$group: {
_id: {a: "$attendance.attendance_code", id: "$_id"}
total: { $sum: 1 },
data: {$first: "$$ROOT"} // if u want to keep document data
}
}
This should give you better performance, i also recommend dropping the projects stages, unless the documents are very large this usually does not end up helping performance but actually hurting it.
I want all the values returned from "Items" and when there is a match I want "isActive" to be true.
exports.listUserItems = function (req, res) {
// Aggregate results
User.aggregate([{
"$match": {
"username": req.params.username
}
}, {
"$lookup": {
"from": "Items",
"localField": "itemIds",
"foreignField": "_id",
"as": "items"
}
}, {
"$unwind": {
"path": "$items"
}
}, {
"$project": {
"item": "$items.item_name",
"isActive": '1',
"_id": 0
}
}], (err, result) => res.json(result));
};
What is the best way to go about accomplishing this?
I was going to return all the items and users items seperately, then compare them and object.value them etc. etc... that seems like overkill. Can it be done on the model side?
I'm unable to post the document structure because stackoverflow doesn't let me, but you should get the idea.
Edit:
User Document
{
"username" : "anonuser",
"items" : [
ObjectId("5ba8345f1e56fe8e6caaaa07"),
ObjectId("5ba706d64e82292e72e9ae71")
]
}
Then I have an "Items" collection which has 3 documents like this.
{
"_id" : ObjectId("5ba706d64e82292e72e9ae71"),
"item_name" : "Salary"
}
My expected json api output is to be.
[{"_id":"5ba706d64e82292e72e9ae71","item_name":"Salary","isActive":true},{"_id":"5ba8345f1e56fe8e6caaaa07","item_name":"Fulltime","isActive":true},{"_id":"5ba9af6c1e56fe8e6cab521e","item_name":"Advisor","isActive":false}]
You can try below aggregation
Items.aggregate([
{ "$lookup": {
"from": "users",
"let": { "itemsId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$in": ["$$itemsId", "$items"] }}},
{ "$project": { "isActive": { "$literal": true }}}
],
"as": "items"
}},
{ "$addFields": {
"isActive": {
"$ifNull": [{ "$arrayElemAt": ["$items.isActive", 0] }, { "$literal": false }]
}
}},
{ "$project": { "items": 0 }}
])
I have following json structure in mongo collection-
{
"students":[
{
"name":"ABC",
"fee":1233
},
{
"name":"PQR",
"fee":345
}
],
"studentDept":[
{
"name":"ABC",
"dept":"A"
},
{
"name":"XYZ",
"dept":"X"
}
]
},
{
"students":[
{
"name":"XYZ",
"fee":133
},
{
"name":"LMN",
"fee":56
}
],
"studentDept":[
{
"name":"XYZ",
"dept":"X"
},
{
"name":"LMN",
"dept":"Y"
},
{
"name":"ABC",
"dept":"P"
}
]
}
Now I want to calculate following output.
if students.name = studentDept.name
so my result should be as below
{
"name":"ABC",
"fee":1233,
"dept":"A",
},
{
"name":"XYZ",
"fee":133,
"dept":"X"
}
{
"name":"LMN",
"fee":56,
"dept":"Y"
}
Do I need to use mongo aggregation or is it possible to get above given output without using aggregation???
What you are really asking here is how to make MongoDB return something that is actually quite different from the form in which you store it in your collection. The standard query operations do allow a "limitted" form of "projection", but even as the title on the page shared in that link suggests, this is really only about "limiting" the fields to display in results based on what is present in your document already.
So any form of "alteration" requires some form of aggregation, which with both the aggregate and mapReduce operations allow to "re-shape" the document results into a form that is different from the input. Perhaps also the main thing people miss with the aggregation framework in particular, is that it is not just all about "aggregating", and in fact the "re-shaping" concept is core to it's implementation.
So in order to get results how you want, you can take an approach like this, which should be suitable for most cases:
db.collection.aggregate([
{ "$unwind": "$students" },
{ "$unwind": "$studentDept" },
{ "$group": {
"_id": "$students.name",
"tfee": { "$first": "$students.fee" },
"tdept": {
"$min": {
"$cond": [
{ "$eq": [
"$students.name",
"$studentDept.name"
]},
"$studentDept.dept",
false
]
}
}
}},
{ "$match": { "tdept": { "$ne": false } } },
{ "$sort": { "_id": 1 } },
{ "$project": {
"_id": 0,
"name": "$_id",
"fee": "$tfee",
"dept": "$tdept"
}}
])
Or alternately just "filter out" the cases where the two "name" fields do not match and then just project the content with the fields you want, if crossing content between documents is not important to you:
db.collection.aggregate([
{ "$unwind": "$students" },
{ "$unwind": "$studentDept" },
{ "$project": {
"_id": 0,
"name": "$students.name",
"fee": "$students.fee",
"dept": "$studentDept.dept",
"same": { "$eq": [ "$students.name", "$studentDept.name" ] }
}},
{ "$match": { "same": true } },
{ "$project": {
"name": 1,
"fee": 1,
"dept": 1
}}
])
From MongoDB 2.6 and upwards you can even do the same thing "inline" to the document between the two arrays. You still want to reshape that array content in your final output though, but possible done a little faster:
db.collection.aggregate([
// Compares entries in each array within the document
{ "$project": {
"students": {
"$map": {
"input": "$students",
"as": "stu",
"in": {
"$setDifference": [
{ "$map": {
"input": "$studentDept",
"as": "dept",
"in": {
"$cond": [
{ "$eq": [ "$$stu.name", "$$dept.name" ] },
{
"name": "$$stu.name",
"fee": "$$stu.fee",
"dept": "$$dept.dept"
},
false
]
}
}},
[false]
]
}
}
}
}},
// Students is now an array of arrays. So unwind it twice
{ "$unwind": "$students" },
{ "$unwind": "$students" },
// Rename the fields and exclude
{ "$project": {
"_id": 0,
"name": "$students.name",
"fee": "$students.fee",
"dept": "$students.dept"
}},
])
So where you want to essentially "alter" the structure of the output then you need to use one of the aggregation tools to do. And you can, even if you are not really aggregating anything.