How can I group documents by object in mongodb aggregation? - mongodb

I was grouping documents based on object, it works perfect but I have no idea whether mongo group (object) based on reference or value. I mean, mongo works on reference based or values based while we group.
Anybody knows, what's the behind the hook working of $group? Is mongo group based on reference or value?
Here are dummy documents,
[
{
name: "a",
title: {
title: "b",
order: 1
},
group: "B"
},
{
name: "b",
title: {
title: "b",
order: 1
},
group: "Bs"
},
{
name: "c",
title: {
title: "c",
order: 2
},
group: "B"
}
]
aggregation query,
db.collection.aggregate([
{
"$group": {
"_id": "$title",
"items": {
"$addToSet": {
"name": "$name",
"group": "$group"
}
}
}
}
])
it returns,
[
{
"_id": {
"order": 1,
"title": "b"
},
"items": [
{
"group": "B",
"name": "a"
},
{
"group": "Bs",
"name": "b"
}
]
},
{
"_id": {
"order": 2,
"title": "c"
},
"items": [
{
"group": "B",
"name": "c"
}
]
}
]

MongoDB uses this comparison rules
See how it compares objects, it goes field by field, checks name and then value etc. It goes inside and its recursive.

Related

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

Mongodb querying for all min values that match criteria and indexing

Suppose I had the following:
[
{
"team": "A",
"age": 1,
"name": "Abe"
},
{
"team": "A",
"age": 5,
"name": "Apple"
},
{
"team": "B",
"age": 1,
"name": "Ben"
},
{
"team": "B",
"age": 2,
"name": "Bon"
},
{
"team": "C",
"age": 5,
"name": "Cherry"
}
]
I have the following query:
They must be in either TeamA or TeamB.
After filtering, I want only the youngest.
So in this example, it would return only Abe and Ben.
Preferably, I want it done in a single query. My guess is I have to use an aggregation pipeline, something like
db.People.aggregate(
[
$match: { $or: [{ team: 'A' }, { team: 'B' }] },
// some more stuff
);
Question1: I'm not sure what the next step would be. Could someone point me in the right direction?
Question2:
There may be a million records and I was thinking of adding two index:
Index on Team. I'm thinking this will allow it to filter Teams of interest.
Index on Age so that it can grab only the mins.
Would these indexes help or what kind of indexes should I be looking into?
**Edit: I'm getting closer, but I'm only interested in the records themselves. **
db.collection.aggregate([
{
$match: {
$or: [
{
team: "A"
},
{
team: "B"
}
]
}
},
{
$group: {
_id: "$age",
items: {
$push: "$$ROOT"
}
}
},
{
$sort: {
_id: 1
}
},
{
$limit: 1,
}
])

Query for all documents when any of the array element is found in a field in mongodb

I have a document like this:
[
{
"id": 1,
"active": true,
"key": []
},
{
"id": 2,
"active": true,
"key": [
{
"code": "fake_code",
"ids": [
""
],
"labels": [
"d"
]
}
]
},
{
"id": 3,
"active": true,
"key": [
{
"code": "fake_code",
"ids": [
""
],
"labels": [
"a",
"b",
"c"
]
}
]
}
]
I only want to get the id of the documents in which any of the values of the given array(let's say ["a", "b", "c", "d"]) present in labels field in the documents.
That means, since the given array = ["a", "b", "c", "d"], and if you will see the documents, then you can find the document having id = 2 is having ["d"] in the labels field, and the document having id = 3 is having ["a", "b", "c"] in it's labels.
So, the expected output is like,
[
{
"id": 2
},
{
"id": 3
}
]
Currently, I've been using
db.collection.find({
"key": {
"$all": [
{
"$elemMatch": {
"ids": {
"$in": [
""
]
},
"code": "fake_code",
"labels": {
"$in": [
[
"a",
"b",
"c"
]
]
}
}
}
]
}
},
{
_id: 0,
id: 1
})
This query is able to return me only one document having id = 3, because in this case I am using the given array = ["a", "b", "c"]. But is it possible to get all documents according to the given array(like ["a", "b", "c", "d"]), that means if any document is having at least one matching values of the given array then the query should return the id of those documents?
Thanks
You can use $in. I fyou dont have any condition inside $elemMatch, you can directly access "key.labels":{$in:[....]}
db.collection.find({
key: {
$elemMatch: {
labels: {
$in: [
"a",
"b",
"c",
"d"
]
}
}
}
},
{
_id: 0,
id: 1
})
Working 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

How to get sum of child entries for hierarchical documents?

I have a document of the following form:
{
"name": "root1",
"children": [{
"name": "A",
"children": [{
"name": "A1",
"items": 20
}, {
"name": "A2",
"items": 19
}],
"items": 8
}, {
"name": "B",
"items": 12
}],
"items": 1
}
That is, each level has a "name" field, an "items" field, and optionally a children field. I would like to run a query which returns the total number of items for each root. In this example, it should return (since 20+19+8+12+1=60)
{ "_id" : "root1", "items" : 60 }
However, each document can have arbitrarily many levels. That is, this example has two to three children below the root, but other documents may have more. That is, I cannot do something like
db.myCollection.aggregate( { $unwind : "$children" },
{ $group : { _id : "$name", items: { $sum : "$items" } } } )
What sort of query will work?
There really is no way to descend arrays to arbitrary depths using the aggregation framework. For this sort of structure you need to use mapReduce where you can programatically do this:
db.collection.mapReduce(
function () {
var items = 0;
var action = function(current) {
items += current.items;
if ( current.hasOwnProperty("children") ) {
current.children.forEach(function(child) {
action( child );
});
}
};
action( this );
emit( this.name, items );
},
function(){},
{ "out": { "inline": 1 } }
)
If you do not want mapReduce then consider another structure for your data and do things differently:
{ "name": "root1", "items": 1, "path": [], "root": null },
{ "name": "A", "items": 8, "path": ["root1"], "root": "root1" },
{ "name": "A1", "items": 20, "path": ["root1", "A"], "root": "root1" },
{ "name": "A2", "items": 19, "path": ["root1", "A"], "root": "root1" },
{ "name": "B", "items": 12, "path": ["root1"], "root": "root1" }
Then you just have a simple aggregate:
db.collection.aggregate([
{ "$group": {
"_id": {
"$cond": [
"$root",
"$root",
"$name"
]
},
"items": { "$sum": "$items" }
}}
])
So if you take a different approach to mapping a hierarchy then doing things such as aggregating totals for paths is much easier without the recursive inspection that would otherwise be required.
The approach that you need depends on your actual usage requirements.