My task is to find individual authors(comments.user_id) comment on the article (_id)
{
"_id" : ObjectId("56479d9c8510369a4ecea3a9"),
"comments" : [
{
"text" : "222",
"user_id" : ObjectId("563f2db0e2bf6c431b297d45"),
},
{
"text" : "333",
"user_id" : ObjectId("563f2db0e2bf6c431b297d45"),
},
{
"text" : "444",
"user_id" : ObjectId("563f2db0e2bf6c431b297d45"),
},
{
"text" : "55555",
"user_id" : ObjectId("563e3337e2bf6c431b297d41"),
},
{
"text" : "00000",
"user_id" : ObjectId("563f7c0a8db7963420cd5732"),
},
{
"text" : "00001",
"user_id" : ObjectId("563f7c0a8db7963420cd5732"),
}
]
}
My query looks as follows
db.getCollection('messages').find({
'_id': ObjectId("56479d9c8510369a4ecea3a9"),
'comments.user_id': {$in : [
ObjectId("563e3337e2bf6c431b297d41"),
ObjectId("563f7c0a8db7963420cd5732")
]}
})
It returns all comments. Please help to understand why it happens.
Expected Result
{
"_id" : ObjectId("56479d9c8510369a4ecea3a9"),
"comments" : [
{
"text" : "55555",
"user_id" : ObjectId("563e3337e2bf6c431b297d41"),
},
{
"text" : "00000",
"user_id" : ObjectId("563f7c0a8db7963420cd5732"),
},
{
"text" : "00001",
"user_id" : ObjectId("563f7c0a8db7963420cd5732"),
}
]
}
update query (hopelessness)
db.getCollection('messages').find(
{'_id': ObjectId("56479d9c8510369a4ecea3a9")},
{'comments.user_id': {$in: ["563f2db0e2bf6c431b297d45", "563e3337e2bf6c431b297d41"]}},
{'comments.user_id': {$elemMatch: {$in: ["563f2db0e2bf6c431b297d45", "563e3337e2bf6c431b297d41"]}}}
)
db.getCollection('messages').find(
{'_id': ObjectId("56479d9c8510369a4ecea3a9")},
{comments: {$elemMatch: {'user_id': {$in : [ObjectId("563f2db0e2bf6c431b297d45"), ObjectId("563f7c0a8db7963420cd5732")]}}}}
)
I return only 1 record, and I have all the records from these authors
As you've seen, the $ and $elemMatch projection operators only include the first matching element.
To include multiple, filtered array elements in your projection of the comment array, your can use aggregate with the $redact operator instead of find:
db.getCollection('test').aggregate([
{$match: {
'_id': ObjectId("56479d9c8510369a4ecea3a9"),
'comments.user_id': {$in : [
ObjectId("563e3337e2bf6c431b297d41"),
ObjectId("563f7c0a8db7963420cd5732")
]},
}},
{$redact: {
$cond: {
if: {
$or: [
{$eq: ['$user_id', ObjectId("563e3337e2bf6c431b297d41")]},
{$eq: ['$user_id', ObjectId("563f7c0a8db7963420cd5732")]},
{$not: '$user_id'}
]
},
then: '$$DESCEND',
else: '$$PRUNE'
}
}}
])
$redact iterates over each doc like a tree, keeping or trimming the fields of each doc as it's $cond expression dictates.
It gets a bit tricky to wrap your head around $redact, but it's basically saying that if the level's user_id field matches either of the two ObjectIds in your $in, or it's not present (i.e. as it is in the top level of the doc), include the data, otherwise remove it.
Related
Here is my organization collection.
[{
"_id" : ObjectId("5fd5fc1b9f117029b5233b2e"),
"name" : "ClassA",
"orgMembers" : [
{
"_id" : ObjectId("5fd5fc1b9f117029b5233b2f"),
"userId" : "Ben",
},
{
"_id" : ObjectId("5fd5fc1b9f117029b5233b2f"),
"userId" : "Anton",
}
],
},
{
"_id" : ObjectId("5fd5fc1b9f117029b5233b2e"),
"name" : "ClassA",
"orgMembers" : [
{
"_id" : ObjectId("5fd5fc1b9f117029b5233b2f"),
"userId" : "Ben",
}
],
}]
Each document has properties like _id, name, orgMembers which represent document information.
And orgMembers is the Array of Members (_id, userId) who belongs to organization.
In this collection, I want to fetch the organizations which includes orgMember with Anton as userId and as well orgMembers of fetched organization document should only contain Anton as a orgMember.
Expected Result is likewise
[{
"_id" : ObjectId("5fd5fc1b9f117029b5233b2e"),
"name" : "ClassA",
"orgMembers" : [
{
"_id" : ObjectId("5fd5fc1b9f117029b5233b2f"),
"userId" : "Anton",
}
],
}]
Here ClassA organization has two orgMembers but need to be filtered matching with userId.
I have tried with
documentModel.find({ 'orgMembers.userId': 'Anton' })
But within this query, I get the result like this.
[{
"_id" : ObjectId("5fd5fc1b9f117029b5233b2e"),
"name" : "ClassA",
"orgMembers" : [
// should be omitted
{
"_id" : ObjectId("5fd5fc1b9f117029b5233b2f"),
"userId" : "Ben",
},
// should be contained
{
"_id" : ObjectId("5fd5fc1b9f117029b5233b2f"),
"userId" : "Anton",
}
],
}]
For expected result, orgMember with userId: Ben should be omitted.
How can I get my expected result with mongo query?
I believe this will be worked on your side
db.collection.find({
"orgMembers.userId": "Anton"
},
{
orgMembers: {
"$elemMatch": {
userId: "Anton"
}
}
})
not sure if i quite got your requirement:
but try this if it works
db.collection.find({
"orgMembers.userId": {
$regex: "Anton",
$options: "i"
}
},
{
name: 1,
"orgMembers.$": 1
})
this is to return only the userId you are looking for in orgMembers.if there are more orgmembers they will not be part of output
I have documents in MongoDB, like so:
{
"_id" : ObjectId("5a748c8b178227d602ec9ce8"),
"dateHour" : ISODate("2018-02-02T16:00:00.000Z"),
"data" : [
{
"date" : ISODate("2018-02-02T16:06:35.033Z"),
"cap" : 437105726836.0
},
{
"date" : ISODate("2018-02-02T16:09:25.127Z"),
"cap" : 437316498502.0
},
...
]
}
Using aggregate method (in NodeJS):
db.getCollection('hourly').aggregate([
{$match: {}},
{$unwind: "$data"},
{$project: {_id: 0, date: "$data.date", cap: "$data.cap" } }
])
I get output like:
[
{
"date" : ISODate("2018-02-02T16:06:35.033Z"),
"cap" : 437105726836.0
},
{
"date" : ISODate("2018-02-02T16:09:25.127Z"),
"cap" : 437316498502.0
}
]
QUESTION: What is the most effective way to get output like so:
[
[ISODate("2018-02-02T16:06:35.033Z"), 437105726836.0],
[ISODate("2018-02-02T16:09:25.127Z"), 437316498502.0]
]
?
I can simply add .map(function(item) {return [item.date, item.cap]}) but is this most effective way when working with huge amount of data?
try $project with $map or $reduce
$map
db.col.aggregate(
[
{$project : {
_id : 0,
data : {$map : {input : "$data", as : "d", in : ["$$d.date", "$$d.cap"]}}
}
}
]
)
$reduce
db.col.aggregate(
[
{$project : {
_id : 0,
data : {$reduce : {input : "$data", initialValue : [], in : {$concatArrays : ["$$value", [["$$this.date", "$$this.cap"]]]}}}
}
}
]
).pretty()
output
{
"data" : [
[
ISODate("2018-02-02T16:06:35.033Z"),
437105726836
],
[
ISODate("2018-02-02T16:09:25.127Z"),
437316498502
]
]
}
The root has to be a document, proof:
db.test.aggregate([
{$unwind: "$data"},
{ $replaceRoot: { newRoot: ["$data.date", "$data.cap"] } }
]);
assert: command failed: {
"ok" : 0,
"errmsg" : "'newRoot' expression must evaluate to an object, but resulting value was: [null, null]. Type of resulting value: 'array'. Input document: {date: 2018-02-02T16:06:35.033Z, cap: 4.37106e+11}",
"code" : 40228,
"codeName" : "Location40228"
} : aggregate failed
You could, however, project it into an array within a document:
> db.test.aggregate([
... {$unwind: "$data"},
... { $replaceRoot: { newRoot: {a:["$data.date", "$data.cap"] } }}
... ])
{ "a" : [ ISODate("2018-02-02T16:06:35.033Z"), 437105726836 ] }
{ "a" : [ ISODate("2018-02-02T16:09:25.127Z"), 437316498502 ] }
It's in the projection. Try this:
db.getCollection('hourly').aggregate([
{$match: {}},
{$unwind: "$data"},
{$project: {_id: 0, date: ["$data.date", "$data.cap"] } }
]);
Just in case my syntax is a little off, here is MongoDb documentation to project a new array.
I don't see why you need aggregate.
Why not:
db.getCollection('hourly').find({}, {data: 1}, (err, results) => {
// manage results here.
});
i am not able to query results in this query,
i want result based on detail.type (like fetch record where detail.type="one") and fetch only first 10 records in detail.numbers array
{
"_id" : ObjectId("5a27b609e101b6092b4ebf91"),
"city" : "Mumbai",
"detail" : [
{
"type" : "One",
"name" : "Some name",
"_id" : ObjectId("5a27b609e101b6092b4ebf92"),
"numbers" : [
"72598xxx78",
"81301xxx88",
"83302xxx30",
"84309xxx43",
"85309xxx77",
"86309xxx61",
"87270xxx88",
"85272xxx36",
"88272xxx23",
"85276xxx01"
]
},
{
"name" : "Some name",
"type" : "two",
"_id" : ObjectId("5a28e954d4f5a30527d92a32"),
"contact" : [
"72598xxx78",
"81301xxx88",
"83302xxx30",
"84309xxx43",
"85309xxx77",
"86309xxx61",
"87270xxx88",
"85272xxx36",
"88272xxx23",
"85276xxx01"
]
},
]
}
MongoDB facilitates querying over array elements using $elemMatch operator.
According to description as mentioned into above question as a solution to it please try executing following MongoDB query to fetch required data from MongoDB collection.
db.collection.find({
detail: {
$elemMatch: {
type: 'One'
}
}
}, {
_id: 1,
city: 1,
'detail.$': 1
})
db.collection.aggregate([
{
$project:{
detail:{
$map:{
input:{$filter:{input:"$detail",as:"d",cond:{$eq:["$$d.type","One"]}}},
as:"d",
in:{
"type" : "$$d.type",
"name" : "$$d.name",
"numbers":{$slice:["$$d.numbers",10]}
}
}
}
}
}
])
I am newbie. But I try to learn the most logical ways to write the queries.
Assume I have collection which is as;
{
"id" : NumberInt(1),
"school" : [
{
"name" : "george",
"code" : "01"
},
{
"name" : "michelangelo",
"code" : "01"
}
],
"enrolledStudents" : [
{
"userName" : "elisabeth",
"code" : NumberInt(21)
}
]
}
{
"id" : NumberInt(2),
"school" : [
{
"name" : "leonarda da vinci",
"code" : "01"
}
],
"enrolledStudents" : [
{
"userName" : "michelangelo",
"code" : NumberInt(25)
}
]
}
I want to list occurence of a key with their corresponding code values.
As an example key : michelangelo
To find the occurence of the key, I wrote two differen aggregation queries as;
db.test.aggregate([
{$unwind: "$school"},
{$match : {"school.name" : "michelangelo"}},
{$project: {_id: "$id", "key" : "$school.name", "code" : "$school.code"}}
])
and
db.test.aggregate([
{$unwind: "$enrolledStudents"},
{$match : {"enrolledStudents.userName" : "michelangelo"}},
{$project: {_id: "$id", "key" : "$enrolledStudents.userName", "code" : "$enrolledStudents.code"}}
])
the result of these 2 queries return what I want as;
{ "_id" : 1, "key" : "michelangelo", "code" : "01" }
{ "_id" : 2, "key" : "michelangelo", "code" : 25 }
One of them to search in enrolledStudents, the other one is searching in school field.
Can these 2 queries reduced into more logical query? Or is this the only way to do it?
ps: I am aware that database structure is not logical, but I tried to simulate.
edit
I try to write a query with find.
db.test.find({$or: [{"enrolledStudents.userName" : "michelangelo"} , {"school.name" : "michelangelo"}]}).pretty()
but this returns the whole documents as;
{
"id" : 1,
"school" : [
{
"name" : "george",
"code" : "01"
},
{
"name" : "michelangelo",
"code" : "01"
}
],
"enrolledStudents" : [
{
"userName" : "elisabeth",
"code" : 21
}
]
}
{
"id" : 2,
"school" : [
{
"name" : "leonarda da vinci",
"code" : "01"
}
],
"enrolledStudents" : [
{
"userName" : "michelangelo",
"code" : 25
}
]
}
Mongo 3.4
$match - This stage will keep all the school array and enrolledStudents where there is atleast one embedded document matching both the query condition
$group - This stage will combine all the school and enrolledStudents array to 2d array for each _id in a group.
$project - This stage will $filter the merge array for matching query condition and $map the array to with new labels values array.
$unwind - This stage will flatten the array.
$addFields & $replaceRoot - This stages will add the id field and promote the values array to the top.
db.collection.aggregate([
{$match : {$or: [{"enrolledStudents.userName" : "michelangelo"} , {"school.name" : "michelangelo"}]}},
{$group: {_id: "$id", merge : {$push:{$setUnion:["$school", "$enrolledStudents"]}}}},
{$project: {
values: {
$map:
{
input: {
$filter: {
input: {"$arrayElemAt":["$merge",0]},
as: "onef",
cond: {
$or: [{
$eq: ["$$onef.userName", "michelangelo"]
}, {
$eq: ["$$onef.name", "michelangelo"]
}]
}
}
},
as: "onem",
in: {
key : { $ifNull: [ "$$onem.userName", "$$onem.name" ] },
code : "$$onem.code"}
}
}
}
},
{$unwind: "$values"},
{$addFields:{"values.id":"$_id"}},
{$replaceRoot: { newRoot:"$values"}}
])
Sample Response
{ "_id" : 2, "key" : "michelangelo", "code" : 25 }
{ "_id" : 1, "key" : "michelangelo", "code" : "01" }
Mongo <= 3.2
Replace last two stages of above aggregation with $project to format the response.
{$project: {"_id": 0 , id:"$_id", key:"$values.key", code:"$values.code"}}
Sample Response
{ "_id" : 2, "key" : "michelangelo", "code" : 25 }
{ "_id" : 1, "key" : "michelangelo", "code" : "01" }
You can use $redact instead of $group & match and add $project with $map to format the response.
$redact to go through a document level at a time and perform $$DESCEND and $$PRUNE on the matching criteria.
The only thing to note is usage of $ifNull in the first document level for id so that you can $$DESCEND to embedded document level for further processing.
db.collection.aggregate([
{
$redact: {
$cond: [{
$or: [{
$eq: ["$userName", "michelangelo"]
}, {
$eq: ["$name", "michelangelo"]
}, {
$ifNull: ["$id", false]
}]
}, "$$DESCEND", "$$PRUNE"]
}
},
{
$project: {
id:1,
values: {
$map:
{
input: {$setUnion:["$school", "$enrolledStudents"]},
as: "onem",
in: {
key : { $ifNull: [ "$$onem.userName", "$$onem.name" ] },
code : "$$onem.code"}
}
}
}
},
{$unwind: "$values"},
{$project: {_id:0,id:"$id", key:"$values.key", code:"$values.code"}}
])
I want retrieve the account array if the query find role "elite"
i try with
db.users.aggregate(
{ $match : { "account.role" : "Elite" } }
);
But i have all object...
{
"_id" : ObjectId("7623902143981943"),
"account" : [
{
"role" : "Elite",
"action" : [
"create",
"read",
"update",
"delete"
],
"extra" : {
account:[1,2,3,4]
}
},
{
"role" : "User",
"action" : [
"create",
"read",
"update",
"delete"
],
"extra" : {
account:[10]
}
}
],
}
Can i retrieve only extra Array ( account:[1,2,3,4] ),if it is a positive result from the query? or I have to parse the received object?
(the schema is very simplified, but I have many roles)
You must use $project and $unwind:
//Order of $unwind and $match matters
db.users.aggregate(
{$unwind: "$account"},
{$match : { "account.role" : "Elite" }},
{$project : { "extra.account" : 1}}
);
explanation
$unwind splits the array into different elements. See the effect of
db.users.aggregate({$unwind: "$account"})
then you match the elements with {"account.role": "Elite"}. See the effect of:
db.users.aggregate(
{$unwind: "$account"},
{$match : { "account.role" : "Elite" }}
);
And then you finally project just the desired fields
db.users.aggregate(
{$unwind: "$account"},
{$match : { "account.role" : "Elite" }},
{$project : { "extra.account" : 1}}
);
//You can also remove the _id filed (included by default with:
db.users.aggregate(
{$unwind: "$account"},
{$match : { "account.role" : "Elite" }},
{$project : { _id: 0, "extra.account" : 1}}
);
OLD ANSWER
You must use projection:
db.users.aggregate(
{$match : { "account.role" : "Elite" }},
{$project : { "extra.account" : 1}}
);
Besides, if you are just matching documents, there's no need to use the aggregation framewrok and you can just use:
// No projection here
db.users.find({"account.role" : "Elite"})
or
// Only returns the _id field + "extra.account" field if exists. By default the _id field is included
db.users.find({"account.role" : "Elite"}, { "extra.account" : 1})
// Only returns the "extra.account" field if exists
db.users.find({"account.role" : "Elite"}, { _id: 0, "extra.account" : 1})
Mongodb documentation can be found here and here