I'm using mongoose (with nodejs) to make my queries.
I got the following Schemas for my database model (minified ofc):
var HistorySchema = new Schema({
status : String,
time : Date
});
var TaskSchema = new Schema({
game_id : Schema.Types.ObjectId,
history : [HistorySchema]
}, {collection: 'task'});
Now I want to give an example (insertion) for this which I want to use to show my problem and wish:
(Insertion)
{
"_id" : ObjectId("5772ca87439632101510fa6b"),
"history" :
[
{
"status" : "open",
"time" : ISODate("2016-06-25T12:17:46.982Z")
},
{
"status" : "complete",
"time" : ISODate("2016-06-30T12:17:46.982Z")
}
]
}
so far so good... Now I have a given date, in this case:
ISODate("2016-06-28T12:17:46.982Z")
Now I want to get from my collection all TaskSchema objects including matching HistorySchmea objects from the array. So I want to exclude the not matching part in history array.
I tried many things like $pull operation like
db.task.find(
{
"game_id": ObjectId("57711397893a97aa170aa983"),
"history.time":{
$lte: ISODate("2016-06-28T12:17:46.982Z")
}
},{$pull: {
"history": {
time: {
$gte: ISODate("2016-06-28T12:17:46.982Z")
}
}
}
}
but then I get errors like
Unsupported projection option: $pull: { history: { time: { $gte: new Date(1467116266982) } } }
Does anyone know how I could realize this query? I'm working now for days on this and couldn't find any help.
Thanks in advance!
One solution is to use the aggregation framework :
db.task.aggregate([
{$match: {_id : ObjectId("5772ca87439632101510fa6b")}},
{$unwind : "$history"},
{$match :{"history.time" :
{
$lte: ISODate("2016-06-30T12:17:46.982Z"),
$gte: ISODate("2016-06-01T12:17:46.982Z")
}
}
},
{$group:
{
_id:"$_id",
history: { $push: { status: "$history.status", time: "$history.time" } }
}
}
]);
Related
I am new to MongoDB and Mongoose.
I am practicing some of the queries with the following Schema and Model:
const articleSchema = mongoose.Schema({
title: String,
content: String,
});
const Article = mongoose.model('article', articleSchema);
Let's say I added the following two documents:
{title: "One", content: "Content One"}
{title: "LongerTitleThanOne", content: "Content Two"}
Which query can I use to find all documents where the "title" length is greater than 3?
I tried the following query but it only works with numbers:
Article.find({title:{$gt:3}}, (err,foundItems)=>{});
Many thanks in advance for your help here.
you can use $strLenCP and $expr:
mongo playground
Article.find({
"$expr": {
"$gt": [
{
"$strLenCP": "$title"
},
3
]
}
})
You can also use aggregation query to achieve the result.
Article.aggregate(
[
{
'$project': {
'titleLength': { '$strLenCP': '$name' }
}
}, {
'$match': {
'titleLength': {
'$gt': 2
}
}
}
])
This is not the best way to do this but it works
I advise you to not use this in production.
First get all your data from you database and store it in a variable then use JavaScript filter function to accomplish your task
For Example
let data = model.find({});
let filter_data = data.filter(data.title.length>8);
I have a collection with this data registered
{
_id: 0000120210903, iid: 00001, date: 20210903 }, {
_id: 0000220210903, iid: 00002, date: 20210903 }, {
_id: 0000120210101, iid: 00001, date: 20210101 }
I want to delete all except the document with the most recent date for each iid.
My idea is to group by the date, select the _id of the register with the max(date) and then delete all except this array of _ids. But I can't figure out how to do it.
db.getCollection('testing_data').aggregate(
{ $sort:{ _id:1 }},
{ $group:{
_id:"$iid",
lastId:{ "$last":"$_id" },
}},
{ $project:{ _id: 0, lastId: 1 } }
)
But I don't know where to go from here. Any help is greatly appreciated.
[Solution]
To fix the problem I used an aggregation to recover the combination of the field iid (the identifier shared between documents) and the unique _id as an array.
Then for each element on the array it performs a deleteMany operation on the iid but letting out the most recent _id. In this case I sort by _id because it includes the date but could also sort by the field date.
Due to the high volume of data { allowDiskUse: true } had to be put in the aggregate.
var ids = db.getCollection('testing_data').aggregate([
{ $sort:{ _id:1 }},
{ $group:{
_id:"$iid",
lastId:{ "$last":"$_id" },
}},
{ $project:{ _id: 1, lastId: 1 } }
], { allowDiskUse: true } ).toArray();
ids.forEach(function(x){
db.getCollection('testing_data').deleteMany({ "iid": x._id, "_id": {$ne:x.lastId} })
});
Mine Idea is just stock all _ids at some array that you want to delete, and then use deleteMany with $or filter
db.getCollection("testing_data").find({}).toArray((err,data)=>{
let to_elim = [];
let filtering ={};
for(let el of data){
if(!filtering[el.iid]) filtering[el.iid] = el;
else {
if(filtering[el.iid].date>el.date) to_elim.push({_id:new ObjectID(el._id)})
}
}
db.getCollection("testing_data").deleteMany({$or:to_elim})
})
I hope that all is written rightly, cause wrote all that down on mobile
There is missing some checking if something more recent...
[Solution]
To fix the problem I used an aggregation to recover the combination of the field iid (the identifier shared between documents) and the unique _id as an array.
Then for each element on the array it performs a deleteMany operation on the iid but letting out the most recent _id. In this case I sort by _id because it includes the date but could also sort by the field date.
Due to the high volume of data { allowDiskUse: true } had to be put in the aggregate.
var ids = db.getCollection('testing_data').aggregate([
{ $sort:{ _id:1 }},
{ $group:{
_id:"$iid",
lastId:{ "$last":"$_id" },
}},
{ $project:{ _id: 1, lastId: 1 } }
], { allowDiskUse: true } ).toArray();
ids.forEach(function(x){
db.getCollection('testing_data').deleteMany({ "iid": x._id, "_id": {$ne:x.lastId} })
});
Aggregation calls that are made on parse cloud code are not providing the required result. I'm not sure if this is a problem with the syntax we are using or if there are some missing things we need to get pipeline aggregation calls to work correctly.
For the required aggregation call, we are building a pipeline that uses five different stages. Within the five stages we are using the following four functions: addFields, lookup, unwind, and group. This has been tested on the MongoDB compass application, and the result is displayed correctly. When the aggregations are exported, and converted to what we believe is the correct syntax, the query returns no results.
Simple aggregation pipelines that only use one stage are working fine. This has been tested for both group by and addField calls. It seems that as soon as multiple stages are added to the pipeline there is a failure.
The aggregation call that is produced directly from MongoDB Compass export to Node, is as follows
[
{
'$addFields': {
'user': {
'$substr': [
'$_p_pUser', 6, -1
]
}
}
}, {
'$lookup': {
'from': '_User',
'localField': 'user',
'foreignField': '_id',
'as': 'userobject'
}
}, {
'$addFields': {
'username': '$userobject.username'
}
}, {
'$unwind': {
'path': '$username'
}
}, {
'$group': {
'_id': '$username',
'total': {
'$sum': '$score'
}
}
}
]
The above call, when converted to the syntax provided here (https://docs.parseplatform.org/js/guide/#aggregate), is as follows:
var pipeline = {
addFields : { user: { $substr : ['$_p_pUser', 6, -1]} },
lookup : {
from: '_User',
localField: 'user',
foreignField: 'objectId',
as: 'userobject'
},
addFields : { username: '$userobject.username' },
unwind : { path: '$username' },
group : {
objectId: '$username',
total : {
$sum : '$score'
}
}
};
var pipelineResults = await gameTableQuery.aggregate(pipeline);
This provided no results. It was also tested using the specific field name (pUser) rather than the _p_pUser (required to get the query working in MongoDB Compass).
var pipeline = {
addFields : { user: { $substr : ['$pUser', 6, -1]} },
lookup : {
from: '_User',
localField: 'user',
foreignField: 'objectId',
as: 'userobject'
},
addFields : { username: '$userobject.username' },
unwind : { path: '$username' },
group : {
objectId: '$username',
total : {
$sum : '$score'
}
}
};
A possible issue could be the duplication of the functions addFields. I also attempted the same call, using one addFields call instead.
var pipeline = {
addFields :
{
user: { $substr : ['pUser', 6, -1]},
username: '$userobject.username'
},
lookup : {
from: '_User',
localField: 'user',
foreignField: 'objectId',
as: 'userobject'
},
unwind : { path: '$username' },
group : {
objectId: '$username',
total : {
$sum : '$score'
}
}
};
These calls were done with cloud code and they do not return the required results that were found in MongoDB Compass. No errors are thrown due to syntax, there are simply no results. Are there any restrictions within the parse aggregation calls that would explain why the call is failing?
It seems the issue was due to the substring call.
In MongoDB compass the pUser field was assigned a value that required trimming to access the objectId.
When trying to do the same thing from the parse cloud code aggregate, the trimming wasn't necessary as pUser contains objectId as a child element.
To access the objectId I am now using:
user : '$pUser.objectId'
instead of
user : { $substr : ['pUser', 6, -1]}
I want to show products by ids (56e641d4864e5b780bb992c6 and 56e65504a323ee0812e511f2) and show price after subtracted by discount if available.
I can count the final price using aggregate, but this return all document in a collection, how to make it return only the matches ids
"_id" : ObjectId("56e641d4864e5b780bb992c6"),
"title" : "Keyboard",
"discount" : NumberInt(10),
"price" : NumberInt(1000)
"_id" : ObjectId("56e65504a323ee0812e511f2"),
"title" : "Mouse",
"discount" : NumberInt(0),
"price" : NumberInt(1000)
"_id" : ObjectId("56d90714a48d2eb40cc601a5"),
"title" : "Speaker",
"discount" : NumberInt(10),
"price" : NumberInt(1000)
this is my query
productModel.aggregate([
{
$project: {
title : 1,
price: {
$cond: {
if: {$gt: ["$discount", 0]}, then: {$subtract: ["$price", {$divide: [{$multiply: ["$price", "$discount"]}, 100]}]}, else: "$price"
}
}
}
}
], function(err, docs){
if (err){
console.log(err)
}else{
console.log(docs)
}
})
and if i add this $in query, it returns empty array
productModel.aggregate([
{
$match: {_id: {$in: ids}}
},
{
$project: {
title : 1,
price: {
$cond: {
if: {$gt: ["$discount", 0]}, then: {$subtract: ["$price", {$divide: [{$multiply: ["$price", "$discount"]}, 100]}]}, else: "$price"
}
}
}
}
], function(err, docs){
if (err){
console.log(err)
}else{
console.log(docs)
}
})
Your ids variable will be constructed of "strings", and not ObjectId values.
Mongoose "autocasts" string values for ObjectId into their correct type in regular queries, but this does not happen in the aggregation pipeline, as in described in issue #1399.
Instead you must do the correct casting to type manually:
ids = ids.map(function(el) { return mongoose.Types.ObjectId(el) })
Then you can use them in your pipeline stage:
{ "$match": { "_id": { "$in": ids } } }
The reason is because aggregation pipelines "typically" alter the document structure, and therefore mongoose makes no presumption that the "schema" applies to the document in any given pipeline stage.
It is arguable that the "first" pipeline stage when it is a $match stage should do this, since indeed the document is not altered. But right now this is not how it happens.
Any values that may possibly be "strings" or at least not the correct BSON type need to be manually cast in order to match.
In the mongoose , it work fine with find({_id:'606c1ceb362b366a841171dc'})
But while using the aggregate function we have to use the mongoose object to convert the _id as object eg.
$match: { "_id": mongoose.Types.ObjectId("606c1ceb362b366a841171dc") }
This will work fine.
You can simply convert your id to
let id = mongoose.Types.ObjectId(req.query.id);
and then match
{ $match: { _id: id } },
instead of:
$match: { _id: "6230415bf48824667a417d56" }
use:
$match: { _id: ObjectId("6230415bf48824667a417d56") }
Use this
$match: { $in : [ {_id: mongoose.Types.ObjectId("56e641d4864e5b780bb992c6 ")}, {_id: mongoose.Types.ObjectId("56e65504a323ee0812e511f2")}] }
Because Mongoose autocasts string values for ObjectId into their correct type in regular queries, but this does not happen in the aggregation pipeline. So we need to define ObjectId cast in pipeline queries.
I'm sure it's a simple fix, but I've been pulling my hair out trying to get the syntax correct for a nested object. I'm trying to use it to create a MongoDB document.
The Mongo documents store conversations between two users. Each message in the conversation is stored in separate MongoDB documents, and the conversation document will reference each message that belongs to it.
Here's the Conversation Schema (which I think is OK)
var ConversationSchema = new mongoose.Schema({
participants: [
{
user1: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
},
user2: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
},
},
],
started: Number,
messages: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Message"
}
]
});
And here's one of my many attempts at creating the object to pass into MongoDB.
var conv = {
participants : {
"participants.user1.id" : req.body.senderId,
"participants.user1.username" : req.body.senderName,
"participants.user2.id" : req.body.recipientId,
"participants.user2.username" : req.body.recipientName
},
created : Date.now(),
messages : [] // The message _id is pushed in later.
}
It's the 'participants' bit which is really tripping me up. This data is coming back from the client as it should, but I can't manage to get it into my var conv. What's the correct syntax to create the nested object I need here?
Any guidance would be awesome! Thanks peoples!!
Fixed it! Yep it was just a simple syntax error: here's correct form in case anyone else ends up here.
var conv = {
participants : {
"user1" : {
"id" : req.body.senderId,
"username" : req.body.senderName
},
"user2" : {
"id" : req.body.recipientId,
"username" : req.body.recipientName
}
},
created : Date.now(),
messages : [] // The message _id is pushed in later.
}
Also, pro tip: Go away and do the washing up. Things will be much clearer when you come back to them.