MongoDB - Query on the last object of an array? - javascript

Is there any way in MongoDB in which I can specifically query on last object's key value in an array in a single db query.
For eg: This is my doc in a collection.
{
"com" : [
{ "ts" : 1510830164203, "com" : "com1" },
{ "ts" : 1511242569673, "com" : "connected" },
{ "ts" : 1511244832741, "com" : "vb" }
],
"status" : [
{ "ts" : 1510857000000, "stat" : 3 }
]
}
So as you can see there are multiple objects in com.
How can I query on last object's ts(timestamp) or I want to check is last com inserted in between today's date or not.
I have already gone through this link. But didn't find the appropriate solution.
Any help can be appreciated.

You can use $arrayElemAt to get the last element and then match applied in aggregation. To get the last element using $arrayElemAt use second value -1 that indicate last element of an array. $arrayElemAt: ["arrayName", -1]. code will be like
db.collectionName.aggregate([
{
$project: {
status: 1,
com: {$arrayElemAt: ["$com", -1]}
}
},
{
$match: {"com.ts": 15115465465}
}
])
N.B: if you want to compare like less than or greater than then use like : $lt, $lte, $gt, or $gte any one that you need
$match: {"com.ts": {$lt: 15115465465}}

var d1 = new Date( parseInt( "Today at 12:00 AM", 16 ) * 1000 )
var d2 = new Date( parseInt( "Tomorrow at 12:00 AM", 16 ) * 1000 )
db.table.find(
{ com: { $elemMatch: {ts:{ $gte: d1, $lt: d2 } } } })
)

db.collection.aggregate(
// Pipeline
[
// Stage 1
{
$match: {
"_id" : ObjectId("5a197a3bde472b16ed9fc28d")
}
},
// Stage 2
{
$unwind: {
path : "$com"
}
},
// Stage 3
{
$sort: {
'com.ts':-1
}
},
// Stage 4
{
$limit: 1
}
]
);

You can use the below project query if you only need to find the last element.
db.collection.find({},{status: 1, com:{$slice: -1}})
More discussion on the similar topic here

Related

Mongo db Aggregate - $sum to add values if between dates

I'm looking to write a query to aggregate the quantity based on dates.
I have documents that look like this:
{
"_id" : 1234,
"itemNumber" : "item 1",
"date" : ISODate("2021-10-26T21:00:00Z"),
"quantity" : 1,
"__v" : 0
}
And a query like this:
//monogoose
myCollection.aggregate().group({
_id: '$itemNumber',
ninetyDays: {
$sum: {
$and: {
$gte: ["date", dayjs().subtract(90, 'd').toDate()],
$lt: ["date", dayjs().toDate()]
}
}
}
})
In the query above ninetyDays is always 0.
I'm basically looking to get the sum of the quantity given a date range.
Help is much appreciated.
Thank you
You can use $cond to sum 1 or 0 if your condition is match.
Assuming your date expression is correct this should works, but don't forger $ in date.
db.collection.aggregate([
{
"$group": {
"_id": "$itemNumber",
"ninetyDays": {
"$sum": {
"$cond": {
"if": {
"$and": [
$gte: ["$date", dayjs().subtract(90, 'd').toDate()],
$lt: ["$date", dayjs().toDate()]
]
},
"then": 1,
"else": 0
}
}
}
}
}
])
Example here where I've used mongo $$NOW but if your date works is easier to use your code.

How to implement mongo ttl in nodejs? [duplicate]

I have a simple schema like:
{
_id: String, // auto generated
key: String, // there is a unique index on this field
timestamp: Date() // set to current time
}
Then I set the TTL index like so:
db.sess.ensureIndex( { "timestamp": 1 }, { expireAfterSeconds: 3600 } )
I expect the record to removed after 1 hour but it is never removed.
I flipped on verbose logging and I see the TTLMonitor running:
Tue Sep 10 10:42:37.081 [TTLMonitor] TTL: { timestamp: 1.0 } { timestamp: { $lt: new Date(1378823557081) } }
Tue Sep 10 10:42:37.081 [TTLMonitor] TTL deleted: 0
When I run that query myself I see all my expired records coming back:
db.sess.find({ timestamp: { $lt: new Date(1378823557081) }})
...
Any ideas? I'm stumped.
EDIT - Example document below
{ "_id" : "3971446b45e640fdb30ebb3d58663807", "key" : "6XTHYKG7XBTQE9MJH8", "timestamp" : ISODate("2013-09-09T18:54:28Z") }
Can you show us what the inserted records actually look like?
How long is "never"? Because there's a big warning:
Warning: The TTL index does not guarantee that expired data will be deleted immediately. There may be a delay between the time a document expires and the time that MongoDB removes the document from the database.
Does the timestamp field have an index already?
This was my issue:
I had the index created wrong like this:
{
"v" : 1,
"key" : {
"columnName" : 1,
"expireAfterSeconds" : 172800
},
"name" : "columnName_1_expireAfterSeconds_172800",
"ns" : "dbName.collectionName"
}
When it should have been this: (expireAfterSeconds is a top level propery)
{
"v" : 1,
"key" : {
"columnName" : 1
},
"expireAfterSeconds" : 172800,
"name" : "columnName_1_expireAfterSeconds_172800",
"ns" : "dbName.collectionName"
}

MongoDB Get highest value for each date and put them into an array

What's the best way to go about getting the highest "score" for each "date" and storing them into an array. Let's the say there are over 50 scores for any particular date.
The database looks like this
{
"_id" : ObjectId("5c06b91b583248493294"),
"classid" : "00010109e2",
"score" : 720,
"height" : 1440,
"time" : "2018-11-27T18:05:13.297621823Z",
"__v" : 0
}
And what I'm trying to do is get the highest score for each date, from a date-range of around 2 weeks and store one highest score for each date in a simple array.
I've tried loads of things, including recursion to no avail.
Can anyone shed any light on this, or point me in the right direction?
You should look into mongodb aggregation and especially $group operator, it usually used to perform such kind of operations.
In this case your code will look like that:
Scores.aggregate([
{
$match: {
time: {
$gte: startOfSomePeriod,
$lte: endOfSomePeriod
}
}
},
{
$group: {
_id: {
year: { $year: "$time" },
month: { $month: "$time" },
day: { $dayOfMonth: "$time" }
},
score: { $max: "$score" }
}
}
]);
P.S. You can use mongooses default createdAt timestamp by simply adding an option to your schema definition.

Group by Multiple Fields In Meteor

What I am trying to achieve is, within a given date range, I want to group Users by First time and then by userId.
I tried below query to group by Multiple Fields,
ReactiveAggregate(this, Questionaire,
[
{
"$match": {
"time": {$gte: fromDate, $lte: toDate},
"userId": {'$regex' : regex}
}
},
{
$group : {
"_id": {
"userId": "$userId",
"date": { $dateToString: { format: "%Y-%m-%d", date: "$time" } }
},
"total": { "$sum": 1 }
}
}
], { clientCollection: "Questionaire" }
);
But When I execute it on server side, it shows me below error,
Exception from sub Questionaire id kndfrx9EuZ5EejKmE
Error: Meteor does not currently support objects other than ObjectID as ids
The message actually says it all, since the "compound" _id value that you are generating via the $group is not actually supported in the clientCollection output which will be published.
The simple solution of course is to not use the resulting _id value from $group as the "final" _id value in the generated output. So just as the example on the project README demonstrates, simply add a $project that removes the _id and renames the present "compound grouping key" as a different property name:
ReactiveAggregate(this, Questionaire,
[
{
"$match": {
"time": {$gte: fromDate, $lte: toDate},
"userId": {'$regex' : regex}
}
},
{
$group : {
"_id": {
"userId": "$userId",
"date": { $dateToString: { format: "%Y-%m-%d", date: "$time" } }
},
"total": { "$sum": 1 }
}
},
// Add the reshaping to the end of the pipeline
{
"$project": {
"_id": 0, // remove the _id, this will be automatically filled
"userDate": "$_id", // the renamed compound key
"total": 1
}
}
], { clientCollection: "Questionaire" }
);
The field order will be different because MongoDB keeps the existing fields ( i.e "total" in this example ) and then adds any new fields to the document. You can cou[nter that by using different field names in the $groupand $project stages rather than the 1 inclusive syntax.
Without such a plugin, this sort of reshaping is something that has been regularly done in the past, by again renaming the output _id and supplying a new _id value compatible with what meteor client collections expect to be present in this property.
On closer inspection of how the code is implemented, it is probably best to actually supply an _id value in the results because the plugin actually makes no effort to create an _id value.
So simply extracting one of the existing document _id values in the grouping should be sufficient. So I would add a $max to do this, and then replace the _id in the $project:
ReactiveAggregate(this, Questionaire,
[
{
"$match": {
"time": {$gte: fromDate, $lte: toDate},
"userId": {'$regex' : regex}
}
},
{
$group : {
"_id": {
"userId": "$userId",
"date": { $dateToString: { format: "%Y-%m-%d", date: "$time" } }
},
"maxId": { "$max": "$_id" },
"total": { "$sum": 1 }
}
},
// Add the reshaping to the end of the pipeline
{
"$project": {
"_id": "$maxId", // replaced _id
"userDate": "$_id", // the renamed compound key
"total": 1
}
}
], { clientCollection: "Questionaire" }
);
This could be easily patched in the plugin by replacing the lines
if (!sub._ids[doc._id]) {
sub.added(options.clientCollection, doc._id, doc);
} else {
sub.changed(options.clientCollection, doc._id, doc);
}
With using Random.id() when the document(s) output from the pipeline did not already have an _id value present:
if (!sub._ids[doc._id]) {
sub.added(options.clientCollection, doc._id || Random.id(), doc);
} else {
sub.changed(options.clientCollection, doc._id || Random.id(), doc);
}
But that might be a note to the author to consider updating the package.

Match Documents where all array members do not contain a value

MongoDB selectors become quickly complicated, especially when you come from mySQL using JOIN and other fancy keywords. I did my best to make the title of this question as clear as possible, but failed miserably.
As an example, let a MongoDB collection have the following schema for its documents:
{
_id : int
products : [
{
qte : int
status : string
},
{
qte : int
status : string
},
{
qte : int
status : string
},
...
]
}
I'm trying to run a db.collection.find({ }) query returning documents where all products do not have the string "finished" as status. Please note that the products array has a variable length.
We could also say we want all documents that has at least one product with a status that is not "finished".
If I were to run it as a Javascript loop, we would have something like the following :
// Will contain queried documents
var matches = new Array();
// The documents variable contains all documents of the collection
for (var i = 0, len = documents.length; i < len; i++) {
var match = false;
if (documents[i].products && documents[i].products.length !== 0) {
for (var j = 0; j < documents[i].products; j++) {
if (documents[i].products[j].status !== "finished") {
match = true;
break;
}
}
}
if (match) {
matches.push(documents[i]);
}
}
// The previous snippet was coded directly in the Stack Overflow textarea; I might have done nasty typos.
The matches array would contain the documents I'm looking for. Now, I wish there would be a way of doing something similar to collection.find({"products.$.status" : {"$ne":"finished"}}) but MongoDB hates my face when I do so.
Also, documents that do not have any products need to be ignored, but I already figured this one out with a $and clause. Please note that I need the ENTIRE document to be returned, not just the product array. If a document has products that are not "finished", then the entire document should be present. If a document has all of its products set at "finished", the document is not returned at all.
MongoDB Version: 3.2.4
Example
Let's say we have a collection that contains three documents.
This one would match because one of the status is not "finished".
{
_id : 1,
products : [
{
qte : 10,
status : "finished"
},
{
qte : 21,
status : "ongoing"
},
]
}
This would not match because all statuses are set to "finished"
{
_id : 2,
products : [
{
qte : 35,
status : "finished"
},
{
qte : 210,
status : "finished"
},
{
qte : 2,
status : "finished"
},
]
}
This would also not match because there are no products. It would also not match if the products field was undefined.
{
_id : 3,
products : []
}
Again, if we ran the query in a collection that had the three documents in this example, the output would be:
[
{
_id : 1,
products : [
{
qte : 10,
status : "finished"
},
{
qte : 21,
status : "ongoing"
},
]
}
]
Only the first document gets returned because it has at least one product that doesn't have a status of "finished", but the last two did not make the cut since they either have all their products' statuses set as "finished", or don't have any products at all.
Try following query. It's fetching documents where status is not equals to "finished"
Note: This query will work with MongoDB 3.2+ only
db.collection.aggregate([
{
$project:{
"projectid" : 1,
"campname" : 1,
"campstatus" : 1,
"clientid" : 1,
"paymentreq" : 1,
products:{
$filter:{
input:"$products",
as: "product",
cond:{$ne: ["$$product.status", "finished"]}
}
}
}
},
{
$match:{"products":{$gt: [0, {$size:"products"}]}}
}
])
You need .aggregate() rather than .find() here. That is the only way to determine if ALL elements actually don't contain what you want:
// Sample data
db.products.insertMany([
{ "products": [
{ "qte": 1 },
{ "status": "finished" },
{ "status": "working" }
]},
{ "products": [
{ "qte": 2 },
{ "status": "working" },
{ "status": "other" }
]}
])
Then the aggregate operation with $redact:
db.products.aggregate([
{ "$redact": {
"$cond": {
"if": {
"$anyElementTrue": [
{ "$map": {
"input": "$products",
"as": "product",
"in": {
"$eq": [ "$$product.status", "finshed" ]
}
}}
]
},
"then": "$$PRUNE",
"else": "$$KEEP"
}
}}
])
Or alternately you can use the poorer and slower cousin with $where
db.products.find(function(){
return !this.products.some(function(product){
return product.status == "finished"
})
})
Both return just the one sample document:
{
"_id" : ObjectId("56fb4791ae26432047413455"),
"products" : [
{
"qte" : 2
},
{
"status" : "working"
},
{
"status" : "other"
}
]
}
So the $anyElementTrue with the $map input or the .some() are basically doing the same thing here and evaluating if there was any match at all. You use the "negative" assertion to "exclude" documents that actually find a match.

Categories

Resources