what is syntactically wrong with this query in MongoDB? [duplicate] - mongodb

This question already has answers here:
Syntax of $or in mongoDB
(1 answer)
MongoDB aggregation framework match OR
(2 answers)
Closed last month.
{a: {b: 1, c: 2}}
db.getCollection("col").aggregate([
{ $match: { "a.b": { $or: [2, 3] } } },
])
It is complaining that it doesn't recognize the $or operator.

The documentation for the $match stage states that:
The query syntax is identical to the read operation query syntax
If we inspect the documentation for the $or operator, you need to pass it expressions, or more specifically, expression objects. Expression objects have the form { <field1>: <expression1>, ... }.
So the correct way to perform this query using $or would be to do:
db.collection.aggregate([
{
"$match": {
"$or": [
{
"a.b": 2
},
{
"a.b": 3
}
]
}
}
])
Or as the other answer suggested, if both expressions are inspecting the same field, you can use $in. The syntax you would use for $in is more like what you tried initially: { field: { $in: [<value1>, <value2>, ... <valueN> ] } }. Put together it might look like:
db.collection.aggregate([
{
"$match": {
"a.b": {
$in: [
2,
3
]
}
}
}
])

Related

MongoDB - How to match a single property value

Suppose a collection contains the following 3 documents:
[
{ "_id": 1, "prop": 1 },
{ "_id": 2, "prop": 4 },
{ "_id": 3, "prop": [1, 2, 3] }
]
The query { $match: { prop: 1 } } returns 2 documents, namely 1 and 3. I would have expected it to only return 1.
Is this behaviour documented somewhere or is it a bug?
How could one formulate the query to mean strict equality (as opposed to equality or array-contains)?
I think that MongoDB will always try to match against both scalars and arrays, unless you explicitly rule out the latter:
{ $match : { prop : { $eq : 1, $not: { $type : 'array' } } } }
It doesn't seem to be explicitly documented, but it's implied in the documentation because the syntax for querying scalars for a particular value is the same as the syntax for querying arrays.
I believe the query returns the document with _id: 3 is due to Query an Array for an Element.
The document with _id: 3 will be fulfilled as there is an element matched in the array.
To force strict equality match, I would suggest to provide the aggregation operator in your query, which will include the checking of type.
db.collection.aggregate([
{
$match: {
$expr: {
$eq: [
"$prop",
1
]
}
}
}
])

Combine $in with $split in mongo

I want to combine $in with $split like in the following example, but it fails saying "$in needs an array". I understand the output of $split is an array so I don't know why it fails. Do you know how to solve it or another way to do it?
Thanks
db.mydoc.aggregate([
{
'$match': {
'myid': {
'$in': {
'$split': [
'136618,136620,136622',
',',
],
},
},
},
},
{
'$project': { ... },
},
]);
This "fail" is the expected behavior, let's understand why.
We must first take a look at the $match behavior as specified in the docs:
$match takes a document that specifies the query conditions. The query syntax is identical to the read operation query syntax; i.e. $match does not accept raw aggregation expressions. Instead, use a $expr query expression to include aggregation expression in $match.
This means when you use $match it uses the query language by default, now the "issue" comes from the difference between the two $in operators the query $in operator (which is being used) and the aggregation $in operator ( which you assume is being used ).
It is true that $split resolves to an array. but $split is also an aggregation operator, now I think this case should throw an error but for some reason as you mentioned this behavior just resolves with no results. the aggregation $in operator however , does accept raw aggregation expressions.
This means all you have to do is convert your $match query to use $expr so you can use the aggregation version of $in within the match, like so:
db.collection.aggregate([
{
"$match": {
$expr: {
$in: [
"$myid",
{
"$split": [
"136618,136620,136622",
","
]
}
]
}
}
}
])
Mongo Playground
#Tom Slabbaert gave a very comprehensive and good answer. Just for sake of completeness, an alternative solution (if you work with Javascript/Mongo shell) is this one:
db.mydoc.aggregate([
{
'$match': {
'myid': { '$in': '136618,136620,136622'.split(',') }
}
},
{
'$project': { ... },
},
]);
Be aware either solutions create an array of strings, i.e. [ "136618", "136620", "136622" ]. It does not match if your collection has numeric values, e.g. { myid: 136618 }
You may use
'136618,136620,136622'.split(',').map(x => NumberInt(x))
or
{ $map: { input: { "$split": ["136618,136620,136622", ","] }, in: { $toInt: "$$this" } } }

MongoDB, finding documents by matching sub elements in an array by several Date conditions [duplicate]

This question already has answers here:
Specify Multiple Criteria for Array Elements
(2 answers)
MongoDB: find value in Array with multiple criteria
(1 answer)
Closed 3 years ago.
I have documents like this:
{
"_id": ID,
"seen_at" : [
ISODate("2018-12-27T17:00:00.000Z"),
ISODate("2019-01-01T01:00:00.000Z")
]
}
I try to select document based on a query into the seen_at elements:
db.collection.aggregate(
[
{
"$match": {
seen_at: {
"$gt": ISODate("2019-01-01T00:00:00.000Z"),
"$lt": ISODate('2019-01-01T00:00:00.001Z')
}
}
}
]
)
I was expecting this query to find only documents that have elements in the seen_at that matche both conditions.
But the above query returns the top-above document (among others also not matching both conditions)
Use $elemMatch if you have multiple criteria to find from array:
db.collection.find({
seen_at: {
$elemMatch: {
"$gt": ISODate("2019-01-01T00:00:00.000Z"),
"$lt": ISODate("2019-01-01T00:00:00.001Z")
}
}
})
Checkout the results in Mongo Playground for find.
If you have to use Aggregate, the $unwind operator can be used:
db.collection.aggregate([
{
$unwind : "$seen_at"
},
{
"$match": {
seen_at: {
"$gt": ISODate("2019-01-01T00:00:00.000Z"),
"$lt": ISODate('2019-01-01T00:00:00.001Z')
}
}
},
{
$group : {
"_id" : "$_id",
"seen_at" : {$push : "$seen_at"}
}
}
])
Checkout the results in Mongo Playground for Aggregate.

MongoDB aggregation project different fields based on the matched condition?

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.

MongoDB mongoose $elemMatch for multiple results [duplicate]

This question already has answers here:
Retrieve only the queried element in an object array in MongoDB collection
(18 answers)
Closed 4 years ago.
I have a query that should return multiple subdocuments from an array in a document, based on a timestamp range criteria. I first choose the main documents by specifying some id's:
In the Mongo Shell it is:
db.fps.find({"_id": {$in: [15,24] }}, {someArray: {$elemMatch: {Timestamp: {$gt: "2018-06-06T18:00:00", $lt:"2018-06-07"}}}}).pretty()
Because of $elemMatch, it returns only the first document that matches my query.
However, I want all relevant documents returned that match the criteria.
How would I have to do the query in mongoose?
Let's say you have a document like this:
db.fps.save({_id: 15, someArray: [ { Timestamp: "2018-06-06T19:00:00" }, { Timestamp: "2018-06-06T19:00:00" }, { Timestamp: "2018-06-07T00:00:00" } ]});
To filter nested array you need aggregation framework, where $match will represent your matching condition and $filter will apply Timestamps comparison. $addFields simply overwrites someArray in result set here
db.fps.aggregate([
{
$match: { "_id": {$in: [15,24] } }
},
{
$addFields: {
someArray: {
$filter: {
input: "$someArray",
as: "doc",
cond: {
$and: [
{ $gt: [ "$$doc.Timestamp", "2018-06-06T18:00:00" ] },
{ $lt: [ "$$doc.Timestamp", "2018-06-07" ] }
]
}
}
}
}
}
])