filtering MongoDB array of Nested objects - mongodb

I'm using MongoDB Compass for my queries while searching through a lot of data that I've inherited and quite often being asked to produce reports on the data for various teams but the documents often have too much data for them to easily parse so I'd like to cut down the data being reported on as much as possible
I've got the following example document
{
"_id": "123456",
"name": "Bob",
"date": "2022-07-01",
"fruit": [
{
"_id": "000001",
"foodName": "apple",
"colour": "red"
},
{
"_id": "000002",
"foodName": "apple",
"colour": "green"
},
{
"_id": "000003",
"foodName": "banana",
"colour": "yellow"
},
{
"_id": "000004",
"foodName": "orange",
"colour": "orange"
}
]
}
using
db.people.find( { "fruit.foodName" : "apple" } )
returns the whole document
I'd like to search for just the apples so that I get the result:
{
"_id": "123456",
"name": "Bob",
"date": "2022-07-01",
"fruit": [
{
"_id": "000001",
"foodName": "apple",
"colour": "red"
},
{
"_id": "000002",
"foodName": "apple",
"colour": "green"
}
]
}
Is that possible?

You will need to use an aggregation for this and use the $filter operator, The reason you can't use the query language for this is because their projection options are limited and only allow the projection of a single array element, because in your case the array can contain more than 1 matching subdocument it won't do.
You can read more about query language projections here
db.collection.aggregate([
{
$match: {
"fruit.foodName": "apple"
}
},
{
$addFields: {
fruit: {
$filter: {
input: "$fruit",
cond: {
$eq: [
"$$this.foodName",
"apple"
]
}
}
}
}
}
])
Mongo Playground

Related

MongoDb Aggregate Total Count Before Grouping

I have an aggregation pipeline that groups objects and holds count for some specific field for grouped objects. You can reproduce the problem here: https://mongoplayground.net/p/2DGaiQDYDBP .
The schema is like this;
[
{
"_id": {
"$oid": "63ce93ffb6e06322db59fdc0"
},
"fruit": "apple",
"source": "tree",
"is_fruit_important": "true"
},
{
"_id": {
"$oid": "63ce93ffb6e06322db59fdc1"
},
"fruit": "orange",
"source": "tree",
"is_fruit_important": "false"
},
]
and the current query groups fruits by the source, and holds the count of important fruits for every group. After applying aggregation I get something like this after query:
[
{
"count": {
"number_of_important_fruits": 1
},
"objects": [
{
"fruit": "apple",
"id": "63ce93ffb6e06322db59fdc0",
"is_fruit_important": "true",
"source": "tree"
},
{
"fruit": "orange",
"id": "63ce93ffb6e06322db59fdc1",
"is_fruit_important": "false",
"source": "tree"
}
],
"source": {
"source-of": "tree"
}
}
]
Is there a way to put the number of all fruits in the database to the response object. For example like this:
{
"total-count": 2,
"result": [
{
"count": {
"number_of_important_fruits": 1
},
"objects": [
{
"fruit": "apple",
"id": "63ce93ffb6e06322db59fdc0",
"is_fruit_important": "true",
"source": "tree"
},
{
"fruit": "orange",
"id": "63ce93ffb6e06322db59fdc1",
"is_fruit_important": "false",
"source": "tree"
}
],
"source": {
"source-of": "tree"
}
}
]
}
They can be handled in separate aggregation pipelines but that's what I would not like to implement. Any help would be highly appreciated.
Add one additional group stage just before the final $project, using $sum with $size for a total count, or add up the important counts for a total important count.
{$group: {
_id: null,
result: {$push: "$$ROOT"},
"count_total": {$sum: {$size: "$objects"}},
"count_important": {$sum: "$count.number_of_important_fruits"}
}},
Playground
You can simply add a $facet stage to push all your results into result. Then perform a $size on result to get total-count.
db.collection.aggregate([
...,
{
"$facet": {
"result": [],
"total-important-count": [
{
$group: {
_id: null,
cnt: {
$sum: "$count.number_of_important_fruits"
}
}
}
]
}
},
{
"$addFields": {
"total-count": {
$size: "$result"
},
"total-important-count": {
$first: "$total-important-count.cnt"
}
}
}
])
Mongo Playground

Delete objects that met a condition inside an array in mongodb

My collection has array "name" with objects inside. I need to remove only those objects inside array where "name.x" is blank.
"name": [
{
"name.x": [
{
"_id": "607e7fcca57aa56e2a06b57b",
"name": "abc",
"type": "123"
}
],
"_id": {
"$oid": "62232cd70ce38c5007de31e6"
},
"qty": "1.0",
"Unit": "pound,lbs"
},
{
"name.x": [
{
"_id": "607e7fcca57aa56e2a06b430",
"name": "xyz",
"type": "123"
}
],
"_id": {
"$oid": "62232cd70ce38c5007de31e7"
},
"qty": "1.0",
"Unit": "pound,lbs"
},{
"name.x": []
,
"_id": {
"$oid": "62232cd70ce38c5007de31e7"
},
"qty": "1.0",
"Unit": "pound,lbs"
}
I tried to get all the ids where name.x is blank using python and used $pull to remove objects base on those ids.But the complete array got deleted.How can I remove the objects that meet the condition.
Think MongoDB update with aggregation pipeline meets your requirement especially to deal with the field name with ..
$set - Update the name array field by $filter name.x field is not an empty array.
db.collection.update({},
[
{
$set: {
name: {
$filter: {
input: "$name",
cond: {
$ne: [
{
$getField: {
field: "name.x",
input: "$$this"
}
},
[]
]
}
}
}
}
}
],
{
multi: true
})
Sample Mongo Playground

Filtering a mongodb query result based on the position of a field in an array

Apologies for the confusing title, I am not sure how to summarize this.
Suppose I have the following list of documents in a collection:
{ "name": "Lorem", "source": "A" }
{ "name": "Lorem", "source": "B" }
{ "name": "Ipsum", "source": "A" }
{ "name": "Ipsum", "source": "B" }
{ "name": "Ipsum", "source": "C" }
{ "name": "Foo", "source": "B" }
as well an ordered list of accepted sources, where lower indexes signify higher priority
sources = ["A", "B"]
My query should:
Take a list of available sources and a list of wanted names
Return a maximum of one document per name.
In case of multiple matches, the document with the most prioritized source should be chosen.
Example:
wanted_names = ['Lorem', 'Ipsum', 'Foo', 'NotThere']
Result:
{ "name": "Lorem", "source": "A" }
{ "name": "Ipsum", "source": "A" }
{ "name": "Foo", "source": "B" }
The results don't necessarily have to be ordered.
Is it possible to do this with a Mongo query alone? If so could someone point me towards a resource detailing how to accomplish it?
My current solution doesn't support a list of names, and instead relies on a Python script to execute multiple queries:
db.collection.aggregate([
{$match: {
"name": "Lorem",
"source": {
$in: sources
}}},
{$addFields: {
"order": {
$indexOfArray: [sources, "$source"]
}}},
{$sort: {
"order": 1
}},
{$limit: 1}
]);
Note: _id fields are omitted in this question for the sake of brevity
How about this: With $group we have $min operator which takes lower source
Note: If you prioritize as ['B', 'A'], use $max then
db.collection.aggregate([
{
$match: {
"name": {
$in: [
"Lorem",
"Ipsum",
"Foo",
"NotThere"
]
},
"source": {
$in: [
"A",
"B"
]
}
}
},
{
$group: {
_id: "$name",
source: {
$min: "$source"
}
}
},
{
$project: {
_id: 0,
name: "$_id",
source: 1
}
}
])
MongoPlayground

MongoDb aggregation framework to group elements of inner array

I'm using MongoDB aggregation framework trying to transform each document:
{
"all": [
{
"type": "A",
"id": "1"
},
{
"type": "A",
"id": "1"
},
{
"type": "B",
"id": "2"
},
{
"type": "A",
"id": "3"
}
]
}
into this:
{
"unique_type_A": [ "3", "1" ]
}
(final result is a collection of n documents with unique_type_A field)
The calculation consists of returning in an array all the uniques types of entities of type A.
I got stuck with $group step, anyone knows how to do it?
To apply this logic to each document, you can use the following;
db.collection.aggregate([
{
$unwind: "$all"
},
{
$match: {
"all.type": "A"
}
},
{
$group: {
_id: {
"type": "$all.type",
"oldId": "$_id"
},
unique_type_A: {
$addToSet: "$all.id"
}
}
},
{
$project: {
_id: 0
}
}
])
Where we first $unwind, to be able to filter and play with each member of all array. Then we just filter the non type:"A" members. The $group stage has the difference with a complex _id, where we utilize the _id of $unwind result, which refers back to the original document, so that we can group the results per original document. Collecting the id from all array with $addToSet to keep only unique values, and voilĂ !
And here is the result per document;
[
{
"unique_type_A": [
"3",
"1"
]
},
{
"unique_type_A": [
"4",
"11",
"5"
]
}
]
Check the code interactively on Mongoplayground

Add conditional and in array of object, mongodb

I have a document like this
{
"_id": {
"$oid": "5c7369826023661073802f63"
},
"participants": [
{
"id": "ABC",
"nickname": "USER1",
},
{
"id": "DEF",
"nickname": "USER2",
}
]},... etc, et
I want to find the record that has the two ids that you provide
I try with this.
moodel.aggregate([
{
$match:{'participants.id':idOne}
},{
$project:{
list:{
$filter:{
input:'$list',
as:'item',
cond: {$eq: ['$$item.participants.id', idTwo]}
}
},
}
}
])
but the result is:
[ { _id: 5c7369826023661073802f63, list: null }]
I want it to return only the record that match the two ids.
use $elematch and $in
https://docs.mongodb.com/manual/reference/operator/query/elemMatch/
https://docs.mongodb.com/manual/reference/operator/query/in/
db.moodel.find({"participants": {$elemMatch: {id: {$in: [idOne, idTwo]}}}})