select docs where all items in an array match a criteria - mongodb

Consider this data in mongodb:
{
"ordernumber" : "161288",
"detail" : [
{
"articlenumber" : "1619",
"price" : 10,
},
{
"articlenumber" : "1620",
"price" : 0,
}
]
}
So basic order data with an array of articles in them.
Now I want to query all orders with where ALL items in detail have a price > 0. So the above one is not selected as 1 item has zero.
This errors ($all needs an array):
db.orders.find({'detail.price': { $all: { $gt: 0 }}})
this finds all orders if at least one price > 0.
db.orders.find({'detail.price': { $gt: 0 }})
How is that possible? Select only docs where all items in an array match a criteria?

playground
db.collection.find({
detail: {
$not: {
"$elemMatch": {
price: { //Negate the condition
$lt: 0
}
}
}
}
})
By this way, you can find all the matching docs with the given condition.
To get lt value
db.collection.find({
detail: {
$not: {
"$elemMatch": {
price: {
$gt: 3
}
}
}
}
})

you can do this using with aggregate.
db.orders.aggregate([
{
$unwind:"$detail"
},
{
$match:{
"detail.price":{$gt:0}
}
}
]);

Related

mongoDB not picking index

I have a mongoDB query as below.
db.mobiles.find({
$or: [
{
status: "roaming"
},
{
status: "local"
},
{
inUse: true
},
{
available: true
},
{
color: true
}
],
updatedAt: {
$lte: 1639992579831
}
})
I have created index as below
db.mobiles.createIndex( { “status” : 1 , “inUse” : 1 , “available” : 1 , “color” : 1 , “updatedAt” : -1} )
When i do explain() , i don't see index getting used. Am i doing something wrong. ?
I can see index got created when i execute db.mobiles.getIndexes()
https://docs.mongodb.com/manual/core/index-compound/#prefixes reads:
For a compound index, MongoDB can use the index to support queries on the index prefixes.
db.mobiles.createIndex( { “status” : 1 , “inUse” : 1 , “available” : 1 , “color” : 1 , “updatedAt” : -1} )
is a compound index, so the only query that can benefit from it is the one that has at least "status" predicate.
Your query does have it, but in the $or statement, means you are happy to select documents with either status as long as at least 1 other $or condition matches, e.g. colour. In this case mongo cannot use the field to search in the index.
This is how it looks in the explain() output:
"parsedQuery": {
"$and": [
{
"$or": [
{
"$or": [
{
"available": {
"$eq": true
}
},
{
"color": {
"$eq": true
}
},
{
"inUse": {
"$eq": true
}
}
]
},
{
"status": {
"$in": [
"local",
"roaming"
]
}
}
]
},
{
"updatedAt": {
"$lte": 1639992579831
}
}
]
},
But it's not the whole story. The query planner analyses such parameters as index selectivity. Considering it's a boolean, there are not much options, and with normal distribution it must be like half of the collection matches the criteria. There is no much benefits of using index in this case.
Considering the query, the only meaningful index would be by updatedAt:
db.mobiles.createIndex( { “updatedAt” : -1} )

MongoQuery to update document using addition

I have the following document in student collection:
{
"uid": 1,
"eng": 70
}
Now I want to add 10 into eng field and want result 80. to do this I am using following query:
db.getCollection('student').aggregate([{$match:{uid:1}},{$set:{eng:{$sum:10}}}])
but it is not working. SO how can add any number in the field to the required output? is any addition query in MongoDB. help me here
I suggest using the $inc operator here:
db.getCollection('student').update(
{ uid: 1 },
{ $inc: { eng: 10 } }
)
SOLUTION #1: Set sum to the same field eng.
db.student.aggregate([
{ $match: { uid: 1 } },
{
$set: {
eng: { $add: ["$eng", 10] } // $sum: ["$eng", 10] Also works;)
}
}
])
Output:
{
"_id" : ObjectId("6065f94abb72032a689ed61d"),
"uid" : 1,
"eng" : 80
}
SOLUTION #2: Set sum to a different field result.
Using $addFields add result filed.
Using $add add 10 to eng and store it in result.
db.student.aggregate([
{ $match: { uid: 1 } },
{
$addFields: {
result: { $add: ["$eng", 10] }
}
}
])
Output:
{
"_id" : ObjectId("6065f94abb72032a689ed61d"),
"uid" : 1,
"eng" : 70,
"result" : 80
}

Mongodb $expr on array

Mongodb: 4.0.13
I'm having troubles in understand and get working $expr with arrays.
Let' start and create a new collection (dbRepeatElement) with following document:
db.testRepeatElement.insert([
{
"data" : {
"FlsResSemires_2" : {
"Sospensione" : [
{
"DataInizio" : 1548806400000,
"DataFine" : 1549065600000,
"Motivazione" : "1"
}
]
}
},
"derived" : {
"DATAFINEANNORIFERIMENTO" : 1609372800000,
"regione190" : "190",
"REGAOEROG" : "190209820300",
"REGASLEROG" : "190209"
}
}
])
In a bigger aggregation, following part is not working:
db.testRepeatElement.aggregate([
{
$match: {
$expr: {
$gt: ["$data.FlsResSemires_2.Sospensione.DataInizio", "$derived.DATAFINEANNORIFERIMENTO"]
}
}
}
])
Result: return a match ( wrong! just check dates)
Reading mongodb documentation seems to be, using combination with arrays, aggregation and $expr does not return expected result and you have to specify with element of the array you want to check, like:
db.testRepeatElement.aggregate([
{
$match: {
$expr: {
$gt: ["$data.FlsResSemires_2.0.Sospensione.DataInizio", "$derived.DATAFINEANNORIFERIMENTO"]
}
}
}
])
Result: return no match (right!)
Question: my requirement is to check every element in the array, so how to solve this, without using $unwind? Why there is this kind of result ?
The $filter aggregation operator is used to do the match operation on array elements. The following aggregation query will result only the Sospensione array elements which match the $gt condition:
db.testRepeatElement.aggregate( [
{
$addFields: {
"data.FlsResSemires_2.Sospensione": {
$filter: {
input: "$data.FlsResSemires_2.Sospensione",
cond: {
$gt: [ "$$this.DataInizio", "$derived.DATAFINEANNORIFERIMENTO" ]
}
}
}
}
},
{
$match: {
$expr: {
$gt: [ { $size: "$data.FlsResSemires_2.Sospensione" }, 0 ]
}
}
}
] ).pretty()

need me use aggregation mongodb in arrays

I need help in aggregate this query, I need aggregate values of debito
{
"_id" : ObjectId("5a088f6584ccb0a665900726"),
"usuario" : "tamura",
"creditos" : [
{
"nome_do_credito" : "credito inicial",
"credito" : 0
}
],
"debitos" : [
{
"nome_do_debito" : "debito inicial",
"debito" : 0
},
{
"nome_do_debito" : "Faculdade",
"debito" : "150.00"
}
]
}
I need the output
debito : 150
(0+150)
You will first need to turn all your debito fields into a numerical type (as in 150.00) since you cannot do Maths on strings (as in "150.00"). And then the following query should do the trick:
db.collection.aggregate({
$project: {
"debitos": {
$sum: "$debitos.debito"
}
}
})
In case you have more than one document in your collection and you want the total sum over all documents you can run this:
db.collection.aggregate({
$unwind: "$debitos" // flatten the "debitos" array
}, {
$group: {
"_id": null, // do not really group, just throw all documents in the same group
"debitos": {
$sum: "$debitos.debito" // sum up all debito fields
}
}
})

How to conditionally project fields during aggregate in mongodb

I have a user document like:
{
_id: "s0m3Id",
_skills: ["skill1", "skill2"],
}
Now I want to unwind this document by the _skills field and add a score for each skill. So my aggregate looks like:
{
'$unwind': {'path': '$_skills', 'preserveNullAndEmptyArrays': true},
},
{
'$project': {
'_skills':
'label': '$_skills',
'skill_score': 1
},
}
},
Sometimes the _skills field can be empty, however in this case I still want the user document to flow through the aggregation - hence the preserveNullAndEmptyArrays parameter. However, the problem I'm having is that it will project a skill_score (though with no label) onto documents which had empty _skills array fields. Thus, when I go to $group the documents later on, those documents now have a non-empty _skills array, containing a single object, namely {skill_score: 1}. This is not what I want - I want documents which had empty (or non-existent) _skills fields to not have any skill_score projected onto them.
So how can I conditionally project a field based on the existence of another field? Using $exists does not help, because that is intended for querying, not for boolean expressions.
Updated
This aggregation will set the value of skill_score to 0 if _skills does not exist, then use $redact to remove the subdocument having skill_score equals to 0:
db.project_if.aggregate([
{
$unwind: {
path: '$_skills',
preserveNullAndEmptyArrays: true,
}
},
{
$project: {
_skills: {
label: '$_skills',
skill_score: {
$cond: {
if: {
$eq: ['$_skills', undefined]
},
then: 0,
else: 1,
}
}
}
}
},
{
$redact: {
$cond: {
if: { $eq: [ "$skill_score", 0 ] },
then: '$$PRUNE',
else: '$$DESCEND'
}
}
}
]);
Result would be like:
[
{ "_id" : '', "_skills" : { "label" : "skill1", "skill_score" : 1 } },
{ "_id" : '', "_skills" : { "label" : "skill2", "skill_score" : 1 } },
{ "_id" : '' },
]