MongoDB query with constraint on sum of attributes - database

I'd like a MongoDB query that returns records where the sum of certain attributes satisfies a constraint, for instance given the following documents:
[
{ _id: 1, q1a: 20, q1b: 50, q1c: 30},
{ _id: 2, q1a: 50, q1b: 30, q1c: 20},
{ _id: 3, q1a: 0, q1b: 0, q1c: 0},
]
Id like to run a query that return all and only docs where (q1a + q1b + q1c) == 100, which in this example is records 1 and 2 above.
Is there a way to express this in MongoDB without using $where and writing the sum as a Javascript function?

You can use $expr along with $sum:
db.collection.find({ $expr: { $eq: [ 100, { $sum: [ "$q1a", "$q1b", "$q1c" ] } ] } })
Mongo Playground

Related

MongoDB merge int arrays in one result

I have this aggregation result
[
{
"ids": [1,100]
},
{
"ids": [200, 100, 3]
}
]
I want to merge them as one result like this:
[
1,
200,
100,
3
]
I have tried group but the result not like I want:
{
$group: {
_id: 1,
merged: {
$push: "$ids"
}
}
}
result:
[
{
"_id": 1,
"merged": [
[1, 100],
[200, 100, 3]
]
}
]
Query
we dont have $concat accumulator
you can do it with $unwind , $group $addToSet
(null or 1 its the same, as long each member is grouped with same key, null is more common)
or you could do it with $push and $reduce $union
here is the first approach looks easier
PlayMongo
aggregate(
[{"$unwind": {"path": "$ids"}},
{"$group": {"_id": null, "ids": {"$addToSet": "$ids"}}},
{"$unset": ["_id"]}])

Mongo project child object but with fewer props

I have a simple document like this in my Collection named 'Partners'
[{
PartnerName: 'Company A',
MarketExpertese: [{Sector: {Code: 1, TotalYears: 1, TotalMonths: 20, TotalClients: 10}},
{Sector: {Code: 2, TotalYears: 2, TotalMonths: 20, TotalClients: 10}},
{Sector: {Code: 3, TotalYears: 3, TotalMonths: 20, TotalClients: 10}}]
}]
The result of desired projection would be:
[{
PartnerName: 'Company A',
MarketExpertese: [{SectorCode: 1, TotalYears: 1},
{SectorCode: 2, TotalYears: 2},
{SectorCode: 3, TotalYears: 3}]
}]
I tried the projection with Map but didnĀ“t work. If I would need just an array of SectorCode (simple array, ex. {[1, 2, 3]} ) I could do, but as I need a prop with 2 values (sectorCode and TotalYears) my map fails...
The closet I could get was this Mongo Playground
we need to use $unwind to get a stream of documents regarding the array MarketExpertese, each document will have an element from that array
then use the $project stage to format the output as we need
then use $group to group the documents we have unwind in first step
the query may look something like this
db.collection.aggregate([
{
"$unwind": "$MarketExpertese" // First we need to use unwind to get a stream of documents regarding this array, each document will have only one element from that array
},
{
"$project": {
PartnerName: 1,
Taste: {
SectorCode: "$MarketExpertese.Sector.Code",
TotalYears: "$MarketExpertese.Sector.TotalYears"
}
}
},
{
"$group": { // then group the results
"_id": "$_id",
PartenerName: {
"$first": "$PartnerName"
},
Taste: {
"$addToSet": "$Taste"
}
}
}
])
and here is a working example on Mongo Playground
Update
you can use $map to do the same functionality
db.collection.aggregate([
{
$project: {
"_id": 0,
"PartnerName": 1,
"Teste": {
"$map": {
"input": "$MarketExpertese",
"as": "elem",
"in": {
SectorCode: "$$elem.Sector.Code",
TotalYears: "$$elem.Sector.TotalYears",
}
}
}
}
}
])
and you can test it here Mongo Playground 2

How to get the intersection of two arrays in MongoDB ($setIntersection does not work well)

I have a simple data structure that defines people and their friends.
{
id: 0,
name: 'a',
friends: [1,2,3]
}
I need to find the common friends of two people. I managed to use the aggregation pipleline get the friends array into an array.
{ "_id" : 0, "friends" : [ [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ], [ 0, 1, 2 ] ] }
So the friends field is a nested array, and I want to get the intersection of its elements.
I tried to use the $setIntersection operation, however I found it does not accept a variable of an array, it only accepts an array of variables. So I have to use something like this to get the result.
{
$project: {
commonFriendIds: {
$setIntersection: [
{ $arrayElemAt: ["$friends", 0] },
{ $arrayElemAt: ["$friends", 1] }
]
}
}
}
It looks ugly, and I have to modify the code to if I need to get the common friends of 3 or more people.
Is there's a better way to accomplish this?
You can use $reduce aggregation operator. Now It will provide you the intersection for all the nested arrays inside the friends array.
db.collection.aggregate([
{ "$project": {
"commonFriendIds": {
"$reduce": {
"input": "$friends",
"initialValue": { "$arrayElemAt": ["$friends", 0] },
"in": { "$setIntersection": ["$$this", "$$value"] }
}
}
}}
])
MongoPlayground

MongoDB $and Query only if the $and is true within the same array index of a document - especially for a doubly nested array [duplicate]

This question already has answers here:
How to search in array of object in mongodb
(5 answers)
Closed 3 years ago.
1. Finding an $and match only if it occurs within the same array element
Let us assume we have these two documents in a mongoDB:
{_id: 1, people: [{height: 10, age: 10}, {height: 5, age: 5}]}
{_id: 2, people: [{height: 10, age: 5}, {height: 5, age: 10}]}
And we want to match every document where there is at least 1 person whose height and age are both >= 10.
My current query is similar to
{$and: [{people.height: {$gte, 10}}, {people.age: {$gte: 10}}]}
However, this is returning both ID 1 and 2, when only id 1 contains a single person who is >=10 for both.
2. Same problem as above, but that list is nested within a second list. Only ID's should be returned if there is at least one person who is both >= 10 age and height.
Lets assume we have these two documents in a mongoDB:
{_id: 1, families: [people: [{height: 10, age: 10}, {height: 5, age: 5}], people: [{height: 0, age: 0}, {height: 0, age: 0}]]}
{_id: 2, families: [people: [{height: 10, age: 5}, {height: 0, age: 0}], people: [{height: 5, age: 10}, {height: 0, age: 0}]]}
How could I construct a query to only match id 1?
3. Building the correct index for this type of query
Currently I have a compound index equivalent to {families.people.height, families.people.age} but I believe this will not be optimized for the query I should be using. Should the index instead be {families.people}?
Note: hypothetically, this query is being run on a mongoDB with ~700,000,000 documents
So, you did not clarify the requirements for data output format. Assuming the data schema returned from the aggregation does not need to be identical to the documents in the collection you could use $unwind.
Example:
db.coll.aggregate([
{ $unwind: "$people" },
{ $addFields: {
isHeightMatch: { $gte: [ "$people.height", 10 ] },
isAgeMatch: { $gte: [ "$people.age", 10 ] }
}
},
{ $match: {
$and: [
{ isHeightMatch: true },
{ isAgeMatch: true }
]
}
},
{ $project: {
"isHeightMatch": 0,
"isAgeMatch": 0
}
}
])
Output:
{ "_id" : 1, "people" : { "height" : 10, "age" : 10 } }
You can use MongoDB $elemMatch to achieve more specific array element matches.
In order to get at least 1 person whose height and age are both >= 10, you can use this query:
{ people: { $elemMatch: { $and: [ {height: {$gte: 10} }, { age: {$gte: 10} } ] } } }
The same thing applies to nested arrays. If the people array is nested in a families array, you can use this query:
{ 'families.people': { $elemMatch: { $and: [ {height: {$gte: 10} }, { age: {$gte: 10} } ] } } }
I hope that helps.

MongoDB Find Exact Array Match but order doesn't matter

I am querying for finding exact array match and retrieved it successfully but when I try to find out the exact array with values in different order then it get fails.
Example
db.coll.insert({"user":"harsh","hobbies":["1","2","3"]})
db.coll.insert({"user":"kaushik","hobbies":["1","2"]})
db.coll.find({"hobbies":["1","2"]})
2nd Document Retrieved Successfully
db.coll.find({"hobbies":["2","1"]})
Showing Nothing
Please help
The currently accepted answer does NOT ensure an exact match on your array, just that the size is identical and that the array shares at least one item with the query array.
For example, the query
db.coll.find({ "hobbies": { "$size" : 2, "$in": [ "2", "1", "5", "hamburger" ] } });
would still return the user kaushik in that case.
What you need to do for an exact match is to combine $size with $all, like so:
db.coll.find({ "hobbies": { "$size" : 2, "$all": [ "2", "1" ] } });
But be aware that this can be a very expensive operation, depending on your amount and structure of data.
Since MongoDB keeps the order of inserted arrays stable, you might fare better with ensuring arrays to be in a sorted order when inserting to the DB, so that you may rely on a static order when querying.
To match the array field exactly Mongo provides $eq operator which can be operated over an array also like a value.
db.collection.find({ "hobbies": {$eq: [ "singing", "Music" ] }});
Also $eq checks the order in which you specify the elements.
If you use below query:
db.coll.find({ "hobbies": { "$size" : 2, "$all": [ "2", "1" ] } });
Then the exact match will not be returned. Suppose you query:
db.coll.find({ "hobbies": { "$size" : 2, "$all": [ "2", "2" ] } });
This query will return all documents having an element 2 and has size 2 (e.g. it will also return the document having hobies :[2,1]).
Mongodb filter by exactly array elements without regard to order or specified order.
Source: https://savecode.net/code/javascript/mongodb+filter+by+exactly+array+elements+without+regard+to+order+or+specified+order
// Insert data
db.inventory.insertMany([
{ item: "journal", qty: 25, tags: ["blank", "red"], dim_cm: [ 14, 21 ] },
{ item: "notebook", qty: 50, tags: ["red", "blank"], dim_cm: [ 14, 21 ] },
{ item: "paper", qty: 100, tags: ["red", "blank", "plain"], dim_cm: [ 14, 21 ] },
{ item: "planner", qty: 75, tags: ["blank", "red"], dim_cm: [ 22.85, 30 ] },
{ item: "postcard", qty: 45, tags: ["blue"], dim_cm: [ 10, 15.25 ] }
]);
// Query 1: filter by exactly array elements without regard to order
db.inventory.find({ "tags": { "$size" : 2, "$all": [ "red", "blank" ] } });
// result:
[
{
_id: ObjectId("6179333c97a0f2eeb98a6e02"),
item: 'journal',
qty: 25,
tags: [ 'blank', 'red' ],
dim_cm: [ 14, 21 ]
},
{
_id: ObjectId("6179333c97a0f2eeb98a6e03"),
item: 'notebook',
qty: 50,
tags: [ 'red', 'blank' ],
dim_cm: [ 14, 21 ]
},
{
_id: ObjectId("6179333c97a0f2eeb98a6e05"),
item: 'planner',
qty: 75,
tags: [ 'blank', 'red' ],
dim_cm: [ 22.85, 30 ]
}
]
// Query 2: filter by exactly array elements in the specified order
db.inventory.find( { tags: ["blank", "red"] } )
// result:
[
{
_id: ObjectId("6179333c97a0f2eeb98a6e02"),
item: 'journal',
qty: 25,
tags: [ 'blank', 'red' ],
dim_cm: [ 14, 21 ]
},
{
_id: ObjectId("6179333c97a0f2eeb98a6e05"),
item: 'planner',
qty: 75,
tags: [ 'blank', 'red' ],
dim_cm: [ 22.85, 30 ]
}
]
// Query 3: filter by an array that contains both the elements without regard to order or other elements in the array
db.inventory.find( { tags: { $all: ["red", "blank"] } } )
// result:
[
{
_id: ObjectId("6179333c97a0f2eeb98a6e02"),
item: 'journal',
qty: 25,
tags: [ 'blank', 'red' ],
dim_cm: [ 14, 21 ]
},
{
_id: ObjectId("6179333c97a0f2eeb98a6e03"),
item: 'notebook',
qty: 50,
tags: [ 'red', 'blank' ],
dim_cm: [ 14, 21 ]
},
{
_id: ObjectId("6179333c97a0f2eeb98a6e05"),
item: 'planner',
qty: 75,
tags: [ 'blank', 'red' ],
dim_cm: [ 22.85, 30 ]
}
]
This query will find exact array with any order.
let query = {$or: [
{hobbies:{$eq:["1","2"]}},
{hobbies:{$eq:["2","1"]}}
]};
db.coll.find(query)
with $all we can achieve this.
Query : {cast:{$all:["James J. Corbett","George Bickel"]}}
Output : cast : ["George Bickel","Emma Carus","George M. Cohan","James J. Corbett"]
Using aggregate this is how I got mine proficient and faster:
db.collection.aggregate([
{$unwind: "$array"},
{
$match: {
"array.field" : "value"
}
},
You can then unwind it again for making it flat array and then do grouping on it.
This question is rather old, but I was pinged because another answer shows that the accepted answer isn't sufficient for arrays containing duplicate values, so let's fix that.
Since we have a fundamental underlying limitation with what queries are capable of doing, we need to avoid these hacky, error-prone array intersections. The best way to check if two arrays contain an identical set of values without performing an explicit count of each value is to sort both of the arrays we want to compare and then compare the sorted versions of those arrays. Since MongoDB does not support an array sort to the best of my knowledge, we will need to rely on aggregation to emulate the behavior we want:
// Note: make sure the target_hobbies array is sorted!
var target_hobbies = [1, 2];
db.coll.aggregate([
{ // Limits the initial pipeline size to only possible candidates.
$match: {
hobbies: {
$size: target_hobbies.length,
$all: target_hobbies
}
}
},
{ // Split the hobbies array into individual array elements.
$unwind: "$hobbies"
},
{ // Sort the elements into ascending order (do 'hobbies: -1' for descending).
$sort: {
_id: 1,
hobbies: 1
}
},
{ // Insert all of the elements back into their respective arrays.
$group: {
_id: "$_id",
__MY_ROOT: { $first: "$$ROOT" }, // Aids in preserving the other fields.
hobbies: {
$push: "$hobbies"
}
}
},
{ // Replaces the root document in the pipeline with the original stored in __MY_ROOT, with the sorted hobbies array applied on top of it.
// Not strictly necessary, but helpful to have available if desired and much easier than a bunch of 'fieldName: {$first: "$fieldName"}' entries in our $group operation.
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$__MY_ROOT",
{
hobbies: "$hobbies"
}
]
}
}
}
{ // Now that the pipeline contains documents with hobbies arrays in ascending sort order, we can simply perform an exact match using the sorted target_hobbies.
$match: {
hobbies: target_hobbies
}
}
]);
I cannot speak for the performance of this query, and it may very well cause the pipeline to become too large if there are too many initial candidate documents. If you're working with large data sets, then once again, do as the currently accepted answer states and insert array elements in sorted order. By doing so you can perform static array matches, which will be far more efficient since they can be properly indexed and will not be limited by the pipeline size limitation of the aggregation framework. But for a stopgap, this should ensure a greater level of accuracy.

Resources