MongoDb Find Collections based on _id and a key in an array - javascript

So if I had something like this:
{
_id:'123',
parts: ['One','Two','Three']
}
So if I wanted parts[0], is it possible to do the find for just that?
So something like:
db.stories.find({_id:'123', part:{$eq:0}})
I know the above is wrong, but I'm wondering if it's more efficient to do the find properly if possibly (criteria related to object/array key), or simply do the broad find and go from there (i.e. use the criteria without object/document.

You can use $project and $arrayElemAt to get the first element in the array
db.stories.aggregate([
{
$match: {_id: '123'}
},{
$project: {part: {$arrayElemAt: ['$parts',0]}}
}
])

If you want to get 0 index element from the parts array you can write in this way
db.sample.find({ _id : "123" } , { parts : { $slice : [0 , 1] } } )

Related

adding dynamic conditions to mongoose find nodejs.. AND, OR in a same query

I am trying to add dynamic conditions using mongoose library
var and_cond = { $and: [] };
var or_cond = { $or: [] };
and_cond.$and.push ({ "doc_no" : /first/i })
or_cond.$or.push ({ "doc_type" : /third/i })
TRModel.find(
and_cond,
or_cond
)
What I expect something which can give me both AND and OR condition work in a query,
The above ended in an abrupt query which is wrong obviously.
title_records.find({ '$and': [ { doc_no: /first/i } ] }, { fields: {
'$or': [ { doc_type: /third/i } ] } })
There are two problems:
You're trying to use two top-level logical operators. To my knowledge, you need to have a single top-level operator from the set of $and, $or, and $nor. You can nest as many as you want within, however.
You're passing in two separate object when what you really want to do is merge the two.
It looks like you're trying to perform your query such that you find all documents that match both the $and condition AND the $or condition. In that case, try the following:
TRModel.find({
$and: [
and_cond,
or_cond
]
});
If, however, you want to use one OR the other, you can simply change the top-level $and to $or:
TRModel.find({
$or: [
and_cond,
or_cond
]
});
You will need to first make sure you are matching the mongoose find parameters which are defined specifically as:
MyModel.find({ <conditions> }, { <projections> }, { <options> }, <callback function>);
e.g.
MyModel.find({ name: /john/i }, null, { skip: 10 }, function (err, docs) {});
So you have to make sure your end result is one object going in into the conditions part of the find.
Next thing is you have to make sure you have one operator from these types:
$and, $or, $not and $not
As your top level one and then you can nest others inside.
Like you could have top level $and with multiple $or inside.

remove document by condition which involve array index in Mongoose [duplicate]

I know that MongoDB supports the syntax find{array.0.field:"value"}, but I specifically want to do this for the last element in the array, which means I don't know the index. Is there some kind of operator for this, or am I out of luck?
EDIT: To clarify, I want find() to only return documents where a field in the last element of an array matches a specific value.
In 3.2 this is possible. First project so that myField contains only the last element, and then match on myField.
db.collection.aggregate([
{ $project: { id: 1, myField: { $slice: [ "$myField", -1 ] } } },
{ $match: { myField: "myValue" } }
]);
You can use $expr ( 3.6 mongo version operator ) to use aggregation functions in regular query.
Compare query operators vs aggregation comparison operators.
For scalar arrays
db.col.find({$expr: {$gt: [{$arrayElemAt: ["$array", -1]}, value]}})
For embedded arrays - Use $arrayElemAt expression with dot notation to project last element.
db.col.find({$expr: {$gt: [{"$arrayElemAt": ["$array.field", -1]}, value]}})
Spring #Query code
#Query("{$expr:{$gt:[{$arrayElemAt:[\"$array\", -1]}, ?0]}}")
ReturnType MethodName(ArgType arg);
Starting Mongo 4.4, the aggregation operator $last can be used to access the last element of an array:
For instance, within a find query:
// { "myArray": ["A", "B", "C"] }
// { "myArray": ["D"] }
db.collection.find({ $expr: { $eq: [{ $last: "$myArray" }, "C"] } })
// { "myArray": ["A", "B", "C"] }
Or within an aggregation query:
db.collection.aggregate([
{ $addFields: { last: { $last: "$myArray" } } },
{ $match: { last: "C" } }
])
use $slice.
db.collection.find( {}, { array_field: { $slice: -1 } } )
Editing:
You can make use of
{ <field>: { $elemMatch: { <query1>, <query2>, ... } } } to find a match.
But it won't give exactly what you are looking for. I don't think that is possible in mongoDB yet.
I posted on the official Mongo Google group here, and got an answer from their staff. It appears that what I'm looking for isn't possible. I'm going to just use a different schema approach.
Version 3.6 use aggregation to achieve the same.
db.getCollection('deviceTrackerHistory').aggregate([
{
$match:{clientId:"12"}
},
{
$project:
{
deviceId:1,
recent: { $arrayElemAt: [ "$history", -1 ] }
}
}
])
You could use $position: 0 whenever you $push, and then always query array.0 to get the most recently added element. Of course then, you wont be able to get the new "last" element.
Not sure about performance, but this works well for me:
db.getCollection('test').find(
{
$where: "this.someArray[this.someArray.length - 1] === 'pattern'"
}
)
You can solve this using aggregation.
model.aggregate([
{
$addFields: {
lastArrayElement: {
$slice: ["$array", -1],
},
},
},
{
$match: {
"lastArrayElement.field": value,
},
},
]);
Quick explanations. aggregate creates a pipeline of actions, executed sequentially, which is why it takes an array as parameter. First we use the $addFields pipeline stage. This is new in version 3.4, and basically means: Keep all the existing fields of the document, but also add the following. In our case we're adding lastArrayElement and defining it as the last element in the array called array. Next we perform a $match pipeline stage. The input to this is the output from the previous stage, which includes our new lastArrayElement field. Here we're saying that we only include documents where its field field has the value value.
Note that the resulting matching documents will include lastArrayElement. If for some reason you really don't want this, you could add a $project pipeline stage after $match to remove it.
For the answer use $arrayElemAt,if i want orderNumber:"12345" and the last element's value $gt than "value"? how to make the $expr? thanks!
For embedded arrays - Use $arrayElemAt expression with dot notation to project last element.
db.col.find({$expr: {$gt: [{"$arrayElemAt": ["$array.field", -1]}, value]}})
db.collection.aggregate([
{
$match: {
$and: [
{ $expr: { $eq: [{ "$arrayElemAt": ["$fieldArray.name", -1] }, "value"] } },
{ $or: [] }
]
}
}
]);

MongoDB: adding sort() to query ruins result set for $and query

I've stumbled upon some very strange behavior with MongoDB. For my test case, I have an MongoDB collection with 9 documents. All documents have the exact same structure, including the fields expired_at: Date and location: [lng, lat].
I now need to find all documents that are not expired yet and are within a bounding box; I show match documents on map. for this I set up the following queries:
var qExpiry = {"expired_at": { $gt : new Date() } };
var qLocation = { "location" : { $geoWithin : { $box : [ [ 123.8766, 8.3269 ] , [ 122.8122, 8.24974 ] ] } } };
var qFull = { $and: [ qExpiry, qLocation ] };
Since the expiry date is long in the past, and when I set the bounding box large enough, the following queries give me all 9 documents as expected:
db.docs.find(qExpiry);
db.docs.find(qLocation);
db.docs.find(qFull);
db.docs.find(qExpiry).sort({"created_at" : -1});
db.docs.find(qLocation).sort({"created_at" : -1});
Now here's the deal: The following query returns 0 documents:
db.docs.find(qFull).sort({"created_at" : -1});
Just adding sort to the AND query ruins the result (please note that I want to sort since I also have a limit in order to avoid cluttering the map on larger scales). Sorting by other fields yield the same empty result. What's going on here?
(Actually even stranger: When I zoom into my map, I sometimes get results for qFull, even with sorting. One could argue that qLocation is faulty. But when I only use qLocation, the results are always correct. And qExpiry is always true for all documents anyway)
You may want to try running the same query using the aggregation framework's $match and $sort pipelines:
db.docs.aggregate([
{ "$match": qFull },
{ "$sort": { "created_at": -1 } }
]);
or implicitly using $and by specifiying a comma-separated list of expressions as in
db.docs.aggregate([
{
"$match": {
"expired_at": { "$gt" : new Date() },
"location" : {
"$geoWithin" : {
"$box" : [
[ 123.8766, 8.3269 ],
[ 122.8122, 8.24974 ]
]
}
}
}
},
{ "$sort": { "created_at": -1 } }
]);
Not really sure why that fails with find()
chridam suggestion using the aggregation framework of MongoDB proved to be the way to go. My working query now looks like this:
db.docs.aggregate(
[
{ $match : { $and : [qExpiry, qLocation]} },
{ $sort: {"created_at": -1} }.
{ $limit: 50 }.
]
);
Nevertheless, if any can point out way my first approach did not work, that would be very useful. Simply adding sort() to a non-empty query shouldn't suddenly return 0 documents. Just to add, since I still tried for a bit, .sort({}) return all documents but was not very useful. Everything else failed including .sort({'_id': 1}).

Mongo aggregation $match equivalent of {$where: "this.field1 !== this.field2"} [duplicate]

This question already has answers here:
MongoDB : aggregation framework : $match between fields
(2 answers)
Closed 6 years ago.
So I have this query,
db.collection.find({$where: "this.field1 !== this.field2"})
But now I need to create a similar query and aggregate the results in a complex query which tried and true, only can be done by using the aggregation pipeline or go "cannon for a fly" and use the mapReduce option.
Since I want to avoid mapReduce, is there a way to achieve something similar to the {$where: "this.field1 !== this.field2"} approach?
Some observations, an answer to the general approach to solve the situation above is most desirable, but in case that is not possible, in order to solve my problem, I can also use the raw value in the query with the following restrictions. I need to find what follows
db.collection.find({ $or :[
{field1: value},
{field2: value}
], $and: [
{ field1: {$not: {$eq: value}}},
{ field2: {$not: {$eq: value}} }
]})
I tried the above query but it discards results that contains the value in either field, and what I want is a way to discard the objects that have the same value in both fields at the same time, but obtain the documents that have the value in either field, I usually prefer an one query approach but after reading a lot, I think the only alternative to avoid mapReduce, is to do something like
db.collection.find({$where: "this.field1 === this.field2"}, function (err, results){
var match = { $or :[
{field1: value},
{field2: value}
], {
_id : { $not : {$in: results.map((x) => x._id)}}
}
db.collection([ {$match: match}, {$project: projectionObject}, ...., etc])
})
So I'm going to use the be solution above, which can be optimized a lot, (I just wrote it while writing the question), but I would like to avoid sending an incredibly big list of objectIds back to the database if at all possible. I will refactor it a bit to use matching operators before the where statement
that's complex case, and maybe you could use a trick with $cond inside $project
var projectDataForMatch = {
$project : {
_id : 1, //list all fields needed here
filterThisDoc : {
$cond : {
if : {
$eq : ["$field1", "$filed2"]
},
then : 1,
else : 0
} //or use compare operator $cmp
}
}
}
var match = {
$match : {
filterThisDoc : 1
}
}
db.col.aggregate([projectDataForMatch, match])
you can extend $cond to fit your needs as desired.

execMatch only returning first result

I am trying to find a particular record (with an id of 1) and then to return only its history field. This history field is an array of objects with a timestamp property. When I return this history field, I want it to contain only objects with timestamp properties greater than 0.
Sample collection
[{
"_id" : 1,
"history" : [
{
"content" : "hello",
"timestamp" : 1394639953878,
"_id" : ObjectId("53208451767743b748ddbd7d")
},
{
"content" : "world",
"timestamp" : 1394639953879,
"_id" : ObjectId("33208451767743b748ddbd7e")
}
]
}]
Thus, according to Mongo and Mongoose documentation, I do something like:
model.find({_id: 1})
.select({ history: { $elemMatch: { timestamp: { $gt: 0 } } } });
The issue: I understand that there will only be one result (since I select the id 1) but I am using $elemMatch to populate its history field with all objects that have a timestamp greater than 0. The issue is that only one object is ever in history.
The $elemMatch projection returns only the first matching element satisfying the condition. Checkout the documentation.
Using aggregate would be the right approach to achieve what you want I think. Please see below:
db.bar.aggregate([
{$match:{_id:1}},
{$unwind:"$history"},
{$match:{"history.timestamp":{$gt:0}}},
{$group:{_id:"$_id", history:{$push:"$history"}}}
])

Categories

Resources