How can I filter document in mongodb? - mongodb

I have a query collection in mongodb which contains document in the below format :
{
_id : ObjectId("61aced92ede..."),
query : "How to solve...?",
answer : []
is_solved : false
}
Now, I want to filter the documents with the following condition
filter all documents that are not solved. (is_solved : true)
filter "n" number of document that are solved.
So, That result will have all unsolved documents and only 10 solved documents in an array.

You can use this aggregation query:
First use $facet to create two ways: The document solved, and document not solved.
Into each way do the necessary $match and $limit the solved documents.
Then concatenate the values using $concatArrays.
db.collection.aggregate([
{
"$facet": {
"not_solved": [
{
"$match": {
"is_solved": false
}
}
],
"solved": [
{
"$match": {
"is_solved": true
}
},
{
"$limit": 10
}
]
}
},
{
"$project": {
"result": {
"$concatArrays": [
"$not_solved",
"$solved"
]
}
}
}
])
Example here where I've used $limit: 1 to see easier.
Also, if you want, you can add $unwind at the end of the aggregation to get values at the top level like this example

Related

if mongodb match inside aggregation returns nothing, how to make a new query?

I use match to select some documents from the collection, and then output all other documents except those found.
If match doesn't find any documents, then I need to display all available documents from the collection.
How can this be done?
Without an example I don't know if I've understood correctly, but you can try this aggregation query (or add this aggregation stages into your query).
The ide is using $facet create two ways:
Frist way: Match the value
Second way: Get everything
And use $project to output one of these options using $cond and $size.
Into the $project if the array returned in the "exists way" is 0 (any result) the result is no_exists(i.e. all values) otherwise is the exists value.
db.collection.aggregate([
{
"$facet": {
"exists": [
{
"$match": {
// your match
}
}
],
"no_exists": []
}
},
{
"$project": {
"result": {
"$cond": {
"if": {
"$eq": [
{
"$size": "$exists"
},
0
]
},
"then": "$no_exists",
"else": "$exists"
}
}
}
}
])
Example here where value exists and output only the value, and here where not exists and output all collection.

match operation for array size gt 0 does not work in aggregation MongoDB

I have an mongo collection called Book.
{
"_id" : "00000000",
"name" : "Book1",
"similarBooks" : [],
"genre" : ""
}
similarBooks is an array in the Book collection which contains other books which are similar to Book1.
I want to find all the books which are having similar books to it. which means i need to match similarBooks array size gt 0 in my aggregation.
I was using the aggregation-
db.Book.aggregate([{
"$match": {
"similarBooks": {
"$gt": {
"$size": 0
}
}
}
}
])
But it is not working.
There is another option of using $expr in the match condition,
db.Book.aggregate([{ {
$match: {
$expr: {
$gt: [{
$size: "$similarBooks"
}, 0]
}
}
}
])
but we can not use $expr while creating the partial index, so I can not use the second option using $expr in my aggregation. Is there any other way I can run the aggregation to find the array size gt 0.
I am using MongoDB shell version v4.2.3.
You can use Mongo's dot notation combined with $exists.
db.Book.aggregate(
[
{
"$match": {
"similarBooks.0": {"$exists": true}
}
}
])

MongoDB, finding documents by matching sub elements in an array by several Date conditions [duplicate]

This question already has answers here:
Specify Multiple Criteria for Array Elements
(2 answers)
MongoDB: find value in Array with multiple criteria
(1 answer)
Closed 3 years ago.
I have documents like this:
{
"_id": ID,
"seen_at" : [
ISODate("2018-12-27T17:00:00.000Z"),
ISODate("2019-01-01T01:00:00.000Z")
]
}
I try to select document based on a query into the seen_at elements:
db.collection.aggregate(
[
{
"$match": {
seen_at: {
"$gt": ISODate("2019-01-01T00:00:00.000Z"),
"$lt": ISODate('2019-01-01T00:00:00.001Z')
}
}
}
]
)
I was expecting this query to find only documents that have elements in the seen_at that matche both conditions.
But the above query returns the top-above document (among others also not matching both conditions)
Use $elemMatch if you have multiple criteria to find from array:
db.collection.find({
seen_at: {
$elemMatch: {
"$gt": ISODate("2019-01-01T00:00:00.000Z"),
"$lt": ISODate("2019-01-01T00:00:00.001Z")
}
}
})
Checkout the results in Mongo Playground for find.
If you have to use Aggregate, the $unwind operator can be used:
db.collection.aggregate([
{
$unwind : "$seen_at"
},
{
"$match": {
seen_at: {
"$gt": ISODate("2019-01-01T00:00:00.000Z"),
"$lt": ISODate('2019-01-01T00:00:00.001Z')
}
}
},
{
$group : {
"_id" : "$_id",
"seen_at" : {$push : "$seen_at"}
}
}
])
Checkout the results in Mongo Playground for Aggregate.

$facet of mongodb returning full sorted documents instead of count based on match

i have a documents as below
{
_id:1234,
userId:90oi,
tag:"self"
},
{
_id:5678,
userId:65yd,
tag:"other"
},
{
_id:9012,
userId:78hy,
tag:"something"
},
{
_id:3456,
userId:60oy,
tag:"self"
},
i needed response like below
[{
tag : "self",
count : 2
},
{
tag : "something",
count : 1
},
{
tag : "other",
count : 1
}
]
i was using $facet to query the documents. but it is returning entire documents not the count. My query is as follows
db.data.aggregate({
$facet: {
categorizedByGrade : [
{ $match: {userId:ObjectId(userId)}},
{$sortByCount: "$tag"}
]
}
})
Let me know what i am doing wrong. Thanks in advance for the help
So you don't need to use $facet for this one - facet is when you really need to process multiple aggregation pipelines in one aggregation query (mongoDB $facet), Please try this :
db.yourCollectionName.aggregate([{$project :{tag :1, _id :0}},{$group :{_id: '$tag',
count: { $sum: 1 }}}, {$project : {tag : '$_id', _id:0, count :1}}])
Explanation :
$project at first point is to retain only needed fields in all documents that way we've less data to process, $group will iterate through all documents to group similar data upon fields specified, While $sum will count the respective number of items getting added through group stage in each set, Finally $project again is used to make the result look like what we needed.
You can retrieve the correct records using facet, please have a look at below query
db.data.aggregate({
$facet: {
categorizedByGrade : [
{
$sortByCount:"$tag"
},
{
$project:{
_id:0,
tag:"$_id",
count:1,
}
}]
}
})

How to filter array in a mongodb query

In mongodb, I have a collection that contains a single document that looks like the following:
{
"_id" : ObjectId("5552b7fd9e8c7572e36e39df"),
"StackSummaries" : [
{
"StackId" : "arn:aws:cloudformation:ap-southeast-2:406119630047:stack/XXXX-30fb22a-285-439ee279-c7c8d36/4ebd8770-f8f4-11e4-bf36-503f2370240f",
"TemplateDescription" : "XXXX",
"StackStatusReason" : "",
"CreationTime" : "2015-05-12T22:14:50.535Z",
"StackName" : "XXXX",
"StackStatus" : "CREATE_COMPLETE"
},
{
"TemplateDescription" : "XXXX",
"StackStatusReason" : "",
"CreationTime" : "2015-05-11T04:02:05.543Z",
"StackName" : "XXXX",
"StackStatus" : "DELETE_COMPLETE",
"StackId" : "arn:aws:cloudformation:ap-southeast-2:406119630047:stack/XXXXX/7c8d04e0-f792-11e4-bb12-506726f15f9a"
},
{ ... },
{ many others }
]
}
ie the imported results of the aws cli command aws cloudformation
list-stacks
I'm trying to find the items of the StackSummaries array that have a StackStatus of CREATE_COMPLETE or UPDATE_COMPLETE. After much experimenting and reading other SO posts I arrived at the following:
db.cf_list_stacks.aggregate( {$match: {"StackSummaries.StackStatus": "CREATE_COMPLETE"}})
However this still returns the whole document (and I haven't even worried about UPDATE_COMPLETE).
I'm coming from an SQL background and struggling with simple queries like this. Any ideas on how to get the information I'm looking for?
SO posts I've looked at:
MongoDB query with elemMatch for nested array data
MongoDB: multiple $elemMatch
$projection vs $elemMatch
Make $elemMatch (projection) return all objects that match criteria
Update
Notes on things I learned while understanding this topic:
aggregate() is just a pipeline (like a Unix shell pipeline) where each $ operator is just another step. And like shell pipelines they can look complex, but you just build them up step by step until you get the results you want
Mongo has a great webinar: Exploring the Aggregation Framework
RoboMongo is a good tool (GPL3) for working with Mongo data and queries
If you only want the object inside the StackSummaries array, you should use the $unwind clause to expand the array, filter the documents you want and then project only the parts of the document that you actually want.
The query would look something like this:
db.cf_list_stacks.aggregate([
{ '$unwind' : '$StackSummaries' },
{ '$match' : { 'StackSummaries.StackStatus' : 'CREATE_COMPLETE' } },
{ '$project' : {
'TemplateDescription' : '$StackSummaries.TemplateDescription',
'StackStatusReason' : '$StackSummaries.StackStatusReason',
...
} }
])
Useful links:
Aggregation pipeline documentation
$unwind Documentation
$project Documentation
With MongoDB 3.4 and newer, you can leverage the $addFields and $filter operators with the aggregation framework to get the desired result.
Consider running the following pipeline:
db.cf_list_stacks.aggregate([
{
"$addFields": {
"StackSummaries": {
"$filter": {
"input": "$StackSummaries",
"as": "el":
"cond": {
"$in": [
"$$el.StackStatus",
["CREATE_COMPLETE", "UPDATE_COMPLETE"]
]
}
}
}
}
}
]);
For MongoDB 3.2
db.cf_list_stacks.aggregate([
{
"$project": {
"StackSummaries": {
"$filter": {
"input": "$StackSummaries",
"as": "el":
"cond": {
"$or": [
{ "$eq": ["$$el.StackStatus", "CREATE_COMPLETE"] },
{ "$eq": ["$$el.StackStatus", "UPDATE_COMPLETE"] }
]
}
}
}
}
}
]);
For MongoDB 3.0 and below
db.cf_list_stacks.aggregate([
{ "$unwind": "$StackSummaries" },
{
"$match": {
"StackSummaries.StackStatus": {
"$in": ["CREATE_COMPLETE", "UPDATE_COMPLETE"]
}
}
},
{
"$group": {
"_id": "$_id",
"StackSummaries": {
"$addToSet": "$StackSummaries"
}
}
}
])
The above pipeline has the $unwind operator which deconstructs the StackSummaries array field from the input documents to output a document for each element. Each output document replaces the array with an element value.
A further filtering is required after the $unwind to get only the documents that pass the given criteria thus a second $match operator pipeline stage follows.
In order to get the original array field after doing the $unwind bit, you would need to group the documents using the $group operator and within the group you can then use the $addToSet array operator to then push the elements into the array.
Based on the criteria that you are trying to find the items of the StackSummaries array that have a StackStatus of CREATE_COMPLETE OR UPDATE_COMPLETE, you could use $elemMatch projection but this won't work with the $in operator as required to get the document with StackStatus of CREATE_COMPLETE OR UPDATE_COMPLETE at this time. There is a JIRA issue for this:
db.cf_list_stacks.find(
{
"StackSummaries.StackStatus": {
"$in": ["CREATE_COMPLETE", "UPDATE_COMPLETE"]
}
},
{
"StackSummaries": {
"$elemMatch": {
"StackStatus": {
"$in": ["CREATE_COMPLETE", "UPDATE_COMPLETE"]
}
}
}
})
This will only give you documents where the StackStatus has the "CREATE_COMPLETE" value.