How to $match an item from the list - database

I am trying to display the items, that have specific name brand.
This is how that field looks like:
"brand" : [
[
"Samsung",
"Iphone",
"Huawei"
]
]
Tried that query, but I get 0 results:
db.collection.aggregate([{$match: { brand: "Samsung" }}])
Any ideas, what's wrong?

Your data model is an array of arrays so you have to deal with two dimensions. You can use $map along with $in first and then use $anyElementTrue to see if there's any sub-array matching your condition:
db.collection.aggregate([
{
$match: {
$expr: {
$anyElementTrue: {
$map: {
input: "$brand",
in: { $in: [ "Samsung", "$$this" ] }
}
}
}
}
},
{
$project: {
_id: 1,
color: 1
}
}
]);
Mongo Playground
EDIT: use $project to display only certain fields

Related

Finding documents in mongodb collection by order of elements index of array field

Array field in collection:
"fruits": [ "fruits": [ "fruits": [
{"fruit1": "banana"}, {"fruit2": "apple"}, {"fruit3": "pear"},
{"fruit2": "apple"}, {"fruit4": "orange"}, {"fruit2": "apple"},
{"fruit3": "pear"}, {"fruit1": "banana"}, {"fruit4": "orange"},
{"fruit4": "orange"} {"fruit3": "pear"} {"fruit1": "banana"}
]
I need to find those documents in collections, where "banana" signed before "apple". Does mongodb allows to compare elements in array just like :
if (fruits.indexOf('banana') < fruits.indexOf('apple')) return true;
Or maybe there is any other method to get result i need?
MongoDB's array query operations do not support any positional search as you want.
You can, however, write a $where query to do what you want:
db.yourCollection.find({
$where: function() {
return (this.fruits.indexOf('banana') < this.fruits.indexOf('apple'))
}
})
Be advised though, you won't be able to use indexes here and the performance will be a problem.
Another approach you can take is to rethink the database design, if you can specify what it is you're trying to build, someone can give you specific advise.
One more approach: pre-calculate the boolean value before persisting to DB as a field and query on true / false.
Consider refactoring your schema if possible. The dynamic field names(i.e. fruit1, fruit2...) make it unnecessarily complicated to construct a query. Also, if you require frequent queries by array index, you should probably store your array entries in individual documents with some sort keys to facilitate sorting with index.
Nevertheless, it is achievable through $unwind and $group the documents again. With includeArrayIndex clause, you can get the index inside array.
db.collection.aggregate([
{
"$unwind": {
path: "$fruits",
includeArrayIndex: "idx"
}
},
{
"$addFields": {
fruits: {
"$objectToArray": "$fruits"
}
}
},
{
"$addFields": {
"bananaIdx": {
"$cond": {
"if": {
$eq: [
"banana",
{
$first: "$fruits.v"
}
]
},
"then": "$idx",
"else": "$$REMOVE"
}
},
"appleIdx": {
"$cond": {
"if": {
$eq: [
"apple",
{
$first: "$fruits.v"
}
]
},
"then": "$idx",
"else": "$$REMOVE"
}
}
}
},
{
$group: {
_id: "$_id",
fruits: {
$push: {
"$arrayToObject": "$fruits"
}
},
bananaIdx: {
$max: "$bananaIdx"
},
appleIdx: {
$max: "$appleIdx"
}
}
},
{
$match: {
$expr: {
$lt: [
"$bananaIdx",
"$appleIdx"
]
}
}
},
{
$unset: [
"bananaIdx",
"appleIdx"
]
}
])
Mongo Playground

MongoDB Aggregation: How to return only the values that don't exist in all documents

Lets say I have an array ['123', '456', '789']
I want to Aggregate and look through every document with the field books and only return the values that are NOT in any documents. For example if '123' is in a document, and '456' is, but '789' is not, it would return an array with ['789'] as it's not included in any books fields in any document.
.aggregate( [
{
$match: {
books: {
$in: ['123', '456', '789']
}
}
},
I don't want the documents returned, but just the actual values that are not in any documents.
Here's one way to scan the entire collection to look for missing book values.
db.collection.aggregate([
{ // "explode" books array to docs with individual book values
"$unwind": "$books"
},
{ // scan entire collection creating set of book values
"$group": {
"_id": null,
"allBooksSet": {
"$addToSet": "$books" // <-- generate set of book values
}
}
},
{
"$project": {
"_id": 0, // don't need this anymore
"missing": { // use $setDifference to find missing values
"$setDifference": [
[ "123", "456", "789" ], // <-- your values go here
"$allBooksSet" // <-- the entire collection's set of book values
]
}
}
}
])
Example output:
[
{
"missing": [ "789" ]
}
]
Try it on mongoplayground.net.
Based on #rickhg12hs's answer, there is another variation replacing $unwind with $reduce, which considered less costly. Two out of Three steps are the same:
db.collection.aggregate([
{
$group: {
_id: null,
allBooks: {$push: "$books"}
}
},
{
$project: {
_id: 0,
allBooksSet: {
$reduce: {
input: "$allBooks",
initialValue: [],
in: {$setUnion: ["$$value", "$$this"]}
}
}
}
},
{
$project: {
missing: {
$setDifference: [["123","456", "789"], "$allBooksSet"]
}
}
}
])
Try it on mongoplayground.net.

Referencing another field of subdocument in `$elemMatch`

I'm trying to perform an $elemMatch in a $match aggregation stage where I want to find if there is a document in an array (commitments) of subdocuments whose property tracksThisWeek is smaller than its frequency property, but I'm not sure how can I reference another field of the subdocument in question, I came up with:
{
$match: {
commitments: {
$elemMatch: {
tracksThisWeek: {
$lt: '$frequency',
},
},
},
},
},
I have a document in the collection that should be returned from this aggregation but isn't, any help is appreciated :)
This can't be done, you can't reference any fields in the query language, What you can do is use $expr with aggregation operators, like this:
db.collection.aggregate([
{
$match: {
$expr: {
$gt: [
{
$size: {
$filter: {
input: "$commitments",
cond: {
$lt: [
"$$this.tracksThisWeek",
"$$this.frequency"
]
}
}
}
},
0
]
}
}
}
])
Mongo Playground

Can't reduce a deeply nested array on MongoDB

I've a Mongo database with documents like these one inside a collection:
{
date:"2019-06-12T00:09:03.000Z",
actions:{
actionDate:"2019-06-12T00:15:25.000Z",
data:{
users:[
[{gender:"Male",age:24},
{gender:"Female",age:25}
],
[{gender:"Male",age:34},
{gender:"Male",age:26}
],
[{gender:"Female",age:19},
{gender:"Male",age:21}
]
]
}
}
}
I would like to summarize the users appearing inside the array users in a single document, like
{
"date":"2019-06-12T00:09:03.000Z",
"actionDate":"2019-06-12T00:15:25.000Z",
"summary":{
"countFemale":2,
"meanFemaleAge":22,
"countMale":4,
"meanMaleAge":26.25
}
}
Some considerations to be taken into account: there could be no cases for one gender and also, the users array might be limited to one or two arrays inside it.
I've tried to solve it using my, now I know, scarce knowledge of Mongo query language but it seems unsolvable to me. Thought this might be useful checking MongoDB: Reduce array of objects into a single object by computing the average of each field but can't catch up with the idea.
Any ideas, please?
Try below query :
db.collection.aggregate([
/** Merge all arrays inside 'users' & push to 'summary' field */
{
$project: {
date: 1,
actionDate: "$actions.actionDate",
summary: {
$reduce: {
input: "$actions.data.users",
initialValue: [],
in: { $concatArrays: ["$$value", "$$this"] },
},
},
},
},
{
$unwind: "$summary",
},
/** Group on 'date' to push data related to same date */
{
$group: {
_id: "$date",
actionDate: {$first: "$actionDate",},
countFemale: {$sum: {$cond: [{$eq: ["$summary.gender", "Female"]},1,0]}},
countMale: {$sum: {$cond: [{$eq: ["$summary.gender", "Male"]},1,0]}},
meanFemaleAge: {$sum: {$cond: [{$eq: ["$summary.gender", "Female"]},"$summary.age",0]}},
meanMaleAge: {$sum: {$cond: [{$eq: ["$summary.gender", "Male"]},"$summary.age",0]}}
}
},
/** Re-create 'meanFemaleAge' & 'meanMaleAge' fields to add mean */
{
$addFields: {
meanFemaleAge: {$cond: [{$ne: ["$meanFemaleAge", 0]},{$divide: ["$meanFemaleAge","$countFemale"]},0]},
meanMaleAge: {$cond: [{$ne: ["$meanMaleAge", 0]},{$divide: ["$meanMaleAge","$countMale"]},0]},
}
}
]);
Test : MongoDB-Playground
Note : No matter what how you do this, I would suggest you to do not implement this kind of operations on entire collection with huge datasets.
We need to perform $reduce operator.
In the first stage, we create separate arrays (Male|Female) and push users according to their gender.
In the second stage, we transform / calculate result.
Try this one:
db.collection.aggregate([
{
$addFields: {
"users": {
$reduce: {
input: "$actions.data.users",
initialValue: {
"Male": [],
"Female": []
},
in: {
Male: {
$concatArrays: [
"$$value.Male",
{
$filter: {
input: "$$this",
cond: {
$eq: [
"$$this.gender",
"Male"
]
}
}
}
]
},
Female: {
$concatArrays: [
"$$value.Female",
{
$filter: {
input: "$$this",
cond: {
$eq: [
"$$this.gender",
"Female"
]
}
}
}
]
}
}
}
}
}
},
{
$project: {
_id: 0,
date: 1,
actionDate: "$actions.actionDate",
summary: {
"countFemale": {
$size: "$users.Female"
},
"meanFemaleAge": {
$avg: "$users.Female.age"
},
"countMale": {
$size: "$users.Male"
},
"meanMaleAge": {
$avg: "$users.Male.age"
}
}
}
}
])
MongoPlayground

mongodb query in two different arrays

I have been going through the mongodb querying inside an array. I have gotten the example they give to work for me for querying a single array. But how would this work if I had two different arrays and wanted to combine them into a single query? for example item1 and item2 were two different arrays.
# example given in mongodb document
db.inventory.find( { qty: { $in: [ 5, 15 ] } } )
# example of query of what I am trying achieve query two arrays
db.inventory.find( { item1: { $in: [ item1_value ] } { item2: { $in: [ item2_value ] } } )
mongodb reference doc: https://docs.mongodb.com/manual/reference/operator/query/in/
I should also mention that I am using mongodb to get the idea of what the command would look like, but ultimately this command should work for pymongo as this command will be executed via pymongo.
# correct query example as given by Moshe
db.inventory.find({
$or: [
{ item1: { $in: [ item1_value ] }},
{ item2: { $in: [ item2_value ] }}
]
});
MongoDB "OR" SYNTAX:
{ $or: [ { <expression1> }, { <expression2> }, ... , { <expressionN> } ] }
In your case:
db.inventory.find({
$or: [
{ item1: { $in: [ item1_value ] },
{ item2: { $in: [ item2_value ] }
]
});

Resources