I have objects that look like this
{
name: 'Object 1',
fruitList: ['apple','pear','orange','grape']
},
{
name: 'Object 2',
fruitList: ['melon','pear','apple','kiwi']
}
I need to retrieve all the objects that have apple before pear in their fruitList, in this example it would mean Object 1 only. Can I do a custom match function that iterates over that list and checks it it matches my criteria ?
You need a mechanism to compare the indexes of the fruits in question and use the comparison as a match condition with the $expr operator. Leverage the aggregation pipeline operators:
$indexOfArray - Searches an array for an occurrence of a specified value and returns the array index (zero-based) of the first occurrence.
$subtract - return the difference between the two indexes. If the value is negative then apple appears before pear in the list.
$lt - the comparison operator to use in $expr query that compares two values and returns true when the first value is less than the second value.
To get a rough idea of these operators at play in an aggregation pipeline, check out the following Mongo Playground.
The actual query you need is as follows:
db.collection.find({
$expr: {
lt: [
{
$subtract: [
{ $indexOfArray: [ '$fruitList', 'apple' ] },
{ $indexOfArray: [ '$fruitList', 'pear' ] }
]
},
0
]
}
})
Mongo Playground
For a generic regex based solution where the fruitList array may contain a basket of assorted fruits (in different cases) for instance:
"fruitList" : [
"mango",
"Apples",
"Banana",
"strawberry",
"peach",
"Pears"
]
The following query can address this challenge:
const getMapExpression = (fruit) => {
return {
$map: {
input: '$fruitList',
as: 'fruit',
in: {
$cond: [
{ $regexMatch: { input: '$$fruit', regex: fruit, options: 'i' } },
{ $literal: fruit },
'$$fruit'
]
}
}
}
}
db.collection.find({
$expr: {
$lt: [
{
$subtract: [
{ $indexOfArray: [ getMapExpression('apple'), 'apple' ] },
{ $indexOfArray: [ getMapExpression('pear'), 'pear' ] }
]
},
0
]
}
})
Related
let's assume I have this object in mongo:
{
"_id": "1",
"Test" {
"Array": [ new NumberInt("10") ]
}
}
Now I would like to use this filter to check if last element in an array satisfy my condition:
{ "Test.Array.-1": { "$gte": new NumberInt("10") } }
But it doesn't work as I expected it to. Is it impossible to filter by last element in an array or do I do something wrong here?
Chain up $gte with $arrayElemAt in a $expr
db.collection.find({
$expr: {
$gte: [
{
$arrayElemAt: [
"$Test.Array",
-1
]
},
10
]
}
})
Mongo Playground
My mongodb, collection name: person
[
{
name:"John",
pens:[
{color:"blue", price:1},
{color:"black",price:2},
{color:"black", price:3},
{color:"red", price:2}
]
},
{
name:"Mary",
pens:[
{color:"green", price:3},
{color:"black",price:2},
{color:"blue", price:1},
{color:"red", price:2}
]
},
{
name:"Tom",
pens:[
{color:"black", price:1},
{color:"black",price:4},
{color:"blue", price:1},
{color:"green", price:3}
]
}
]
First question:
I would like to find out all black pens of "John" and hope the result is like this
[
{color:"black", price:2},
{color:"black, price:3"}
]
However, my query
db.person.find({name:"John"},{pens:{$elemMatch:{color:"black"}}})
It only shows the first record. How do I get the records in the subarray?
[
{color:black,price:2}
]
The second question:
I would like to find out how many black pens John has.
db.person.count({name:"John"},{pens:{$elemMatch:{color:"black"}}})
It shows 1 but the correct number is 2.
How do I get the correct count in the subarray?
The $elemMatch operator project the first matching element from an array based on a condition.
https://docs.mongodb.com/manual/reference/operator/projection/elemMatch/
Workaround: Perform MongoDB aggregation. With $filter operator we take black color pens.
db.person.aggregate([
{
$match: {
name: "John"
}
},
{
$addFields: {
pens: {
$filter: {
input: "$pens",
as: "pens",
cond: {
$eq: [
"$$pens.color",
"black"
]
}
}
}
}
},
{
$unwind: "$pens"
},
{
$replaceWith: "$pens"
}
])
MongoPlayground | Count black pens
I have a Movies collection
...
{
...
"cast":[ "First Actor", "Second Actor" ],
"directors":[ "First Director", "Second Director" ]
},
{
...
"cast": [ "Actor Director", "First Actor" ],
"directors": [ "Actor Director", "Firt Director" ]
}
...
Using aggregation framework I need to get number of documents where at least one value from directors array is also in a cast array. How could I achieve it?
You can use $setIntersection to find common entries in both arrays, then filter documents by $size of the result gt than 0 (means that at least one element is common to arrays), and finally use $count stage to count documents that match this condition.
-- EDIT : Add $addFields stage in case of no array present for cast or directors
In case of any document that doesn't contain cast or directors array, you will get an error for size waiting for an array and getting a null value.
In order to avoid this, you need to add an $addField stage to define empty array instead of null, for cast and directors.
Here's the query :
db.collection.aggregate([
{
$addFields: {
directors: {
$cond: {
if: {
$isArray: "$directors"
},
then: "$directors",
else: []
}
},
cast: {
$cond: {
if: {
$isArray: "$cast"
},
then: "$cast",
else: []
}
}
}
},
{
$match: {
$expr: {
$gt: [
{
$size: {
$setIntersection: [
"$cast",
"$directors"
]
}
},
0
]
}
}
},
{
$count: "have_common_value"
}
])
You can test it here
I've following mongodb query:
db
.getCollection("entries")
.find({
$and: [
{
"array.attribute_1": {
$exists: true,
$not: {
$size: 0
}
}
},
{
$or: [
{ "array.attribute_2": { $exists: true, $size: 0 } },
{ "array.attribute_2": { $exists: true, $eq: {} } }
]
},
]
})
And example of my document:
{
_id: 'foo',
array: [
{attribute_1: [], attribute_2: []},
{attribute_1: ['bar'], attribute_2: []}
]
}
In my understanding my query should find all entries that have at least one element within array that has existent and not empty attribute_1 and existent empty array or empty object attribute_2. However, this query finds all entries that has all elements within array that has existent and not empty attribute_1 and existent empty array or empty object attribute_2. As such, my foo entry won't be found.
What should be the correct formula for my requirements?
$find would find the first document with the matching criteria and in your case that first document contains all the arrays. You either need to use $project with $filter or aggregation with $unwind and $match.
Something like this:
db.collection.aggregate([
{ $unwind: "$array" },
{
$match: {
$and: [
{ "array.attribute_1.0": { $exists: true }},
{
$or: [
{ "array.attribute_2.0": { $exists: false } },
{ "array.attribute_2.0": { $eq: {} } }
]
}
]
}
}
])
You can see it working here
Also since you are trying to find out if array is empty and exists at the same time using .0 with $exists is a quick and one statement way to get the same result as with both $exists and $size.
I am writing a query in MongoDB using aggregation such that if condition 1 matches then I want to project some fields and when condition 2 matches I want to project some different fields and when the third condition 3 reaches I want to project some other different fields.
My Query is like below
{
$match: {
$and: [
{
{field_a: "henesa"}
},
{
$expr: {
$or: [
{ Condition 1}, {Condition 2}, {condition 3}
]
}
}
]
}
},
{$project: { /* Here How To Decide which params to send */}}
Can anyone please tell me how can I do that.
You can use <field>: <expression> syntax of $projection at the projection stage.
In <expression> part you can use conditional operators to project values based on your criteria. E.g.
{ $project: {
field: { $switch: {
branches: [
{ case: Condition 1, then: "$field1" },
{ case: Condition 2, then: "$field2" },
...
]
} }
} }
Or more complex combination of $cond if you need to handle cases when more than one $or conditions met.