I am using Mongo 5.0.6 and have a document structured like this:
[
{
username: "admin",
properties: {
bookmarks: {
value: [
1,
2,
3
]
},
landmark: {
value: [
"home"
]
},
other: {
value: "should not show"
}
}
}
]
I need to query data based on the properties keys, this works fine in my mongo playground: https://mongoplayground.net/p/lEiLeStWGTn when I query for key that contain mark.
db.collection.aggregate([
{
$match: {
username: "admin"
}
},
{
$addFields: {
filtered: {
$filter: {
input: {
$objectToArray: "$properties"
},
cond: {
$regexMatch: {
input: "$$this.k",
regex: "mark"
}
}
}
}
}
},
{
$project: {
_id: 0,
properties: {
$arrayToObject: "$filtered"
}
}
}
])
However when I use it through my mongoose object I get [] even so the data is exactly as in playground. I am using locally the latest mongoose6 and mondodb5.0.6. I get the same [] result when I run the query against a mongodb.com hosted database. What could be the problem?
My javascript query below, I tried both using mongoose and the driver directly, as shown below:
const data= User.collection.aggregate([
{
$match: {
username: "admin"
}
},
{
$addFields: {
filtered: {
$filter: {
input: {
$objectToArray: "$properties"
},
cond: {
$regexMatch: {
input: "$$this.k",
regex: "mark"
}
}
}
}
}
},
{
$project: {
_id: 0,
properties: {
$arrayToObject: "$filtered"
}
}
}
]);
for await (const doc of data) {
console.log(doc);
}
Always gives me:
{ properties: {} }
When I take out the $addFields and $project like this:
const data= User.collection.aggregate([
{
$match: {
username: "admin"
}
}
]);
for await (const doc of data) {
console.log(doc);
}
I get, so the data is there, but aggregation pipeline isn't working:
[
{
_id: "61b9f2f5d2a6021365aae6d6",
username: "admin",
properties: {
bookmarks: {
value: [
1,
2,
3
]
},
landmark: {
value: [
"home"
]
},
other: {
value: "should not show"
}
}
}
]
So the data is there. What am I missing? Do I need to write the query differently?
Related
I searched a lot, tried several ways, but nothing works.
I have this in mongo:
{
id: ObjectId("1234567890"),
answers: [{
id: 111,
deleted:0
},
{
id: 222,
deleted:0
},
{
id: 333,
deleted:1
}]
},
{
id: ObjectId("0987654321"),
answers: [{
id: 111,
deleted:0
},
{
id: 222,
deleted:1
},
{
id: 333,
deleted:1
}]
}
I want the document with ObjectId("1234567890"), and only the answers with delete = 1 ( only the id 333).
I have tryied this:
var query = chatbotMongoModel.find(
{ _id: "1234567890" },
{ answers: {$elemMatch: {deleted: "1"}}}
)
but returns all the answers.. Could you give me some help plase?
thanks!
Rafael
One way of doing this is using mongodb aggregation framework.
var result = await chatbotMongoModel.aggregate([
{
$match: {
id: "1234567890"
}
},
{
"$unwind": {
path: "$answers"
}
},
{
$match: {
"answers.deleted": 1
}
},
{
$group: {
_id: "$id",
answers: {
$push: "$answers"
},
allFields: {
$first: "$$ROOT"
}
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [
"$allFields",
{
"answers": "$answers"
}
]
}
}
}
])
Note that in your sample documents you have id but you use _id in your query, they must match. Also deleted data type is number in sample documents, but you use string value in your query.
Running mongoplayground.
Another alternative solution:
var result = await chatbotMongoModel.aggregate([
{
$match: {
id: "1234567890"
}
},
{
$addFields: {
"answers": {
$filter: {
input: "$answers",
as: "answer",
cond: {
$eq: [
"$$answer.deleted",
1
]
}
}
}
}
}
])
mongoplayground
Thanks for the answers! I found a solution, and im posting here if someone needs this too...
var query = model.aggregate([
{ $match: { '_id': mongoose.Types.ObjectId("1234567890") } },
{
$project: {
answer: {
$filter: {
input: "$answer",
as: "f",
cond: { $eq: ["$$f.deleted", 1] }
}
}
}
}
])
regards!
I am trying to find the latest "order" in "orders" array in the whole collection (Not only in the one object).
Data:
[
{
_id: 1,
orders: [
{
title: 'Burger',
date: {
$date: '2021-07-18T13:12:08.717Z',
},
},
],
},
{
_id: 2,
orders: [
{
title: 'Salad',
date: {
$date: '2021-07-18T13:35:01.586Z',
},
},
],
},
];
Code:
var restaurant = await Restaurant.findOne({
'orders.date': 1,
});
Rather simple:
db.collection.aggregate([
{ $project: { latest_order: { $max: "$orders.date" } } }
])
If you like to get the full order use this:
db.collection.aggregate([
{
$project: {
latest_order: {
$first: {
$filter: {
input: "$orders",
cond: { $eq: [ "$$this.date", { $max: "$orders.date" } ] }
}
}
}
}
},
{ $sort: { "latest_order.date": 1 } },
{ $limit: 1 }
])
Mongo Playground
You have to use aggregation for that
db.collection.aggregate([
{ $unwind: "$orders" },
{ $sort: { "orders.date": -1 } },
{ $limit: 1 },
{
"$group": {
"_id": "$_id",
"orders": { "$first": "$orders" }
}
}
])
Working Mongo playground
Example of the document:
{
postId:'232323',
post:'This is my first post',
commentsOnPost:[
{
commentId:'232323_8888',
comment:'Congrats',
repliesOnPost:[
{
replyId:'232323_8888_66666',
reply:'Thanks',
likesOnReply:['user1','user5','user3'],
}
]
}
]
}
I want to add userid in likesOnReply if users do not exist in likesOnReply, similarly remove userid from likesOnReply if exist.
I have tried like this but not working properly
await collection('post').findOneAndUpdate(
{
postId: postId,
'commentsOnPost.commentId': commentId,
'commentsOnPost.repliesOnPost.replyId': replyId
},
{
$push: { 'commentsOnPost.$[].repliesOnPost.$.likes': userid },
},
);
There is no straight way to do both the operation to pull or push in a single query,
There are 2 approaches,
1) Find and update using 2 queries:
use arrayFilters to updated nested array elements
$push to insert element
$pull to remove element
var post = await collection('post').findOne({
posted: postId,
ommentsOnPost: {
$elemMatch: {
commentId: commentId,
repliesOnPost: {
$elemMatch: {
replyId: replyId
likesOnReply: userid
}
}
}
}
});
var updateOperator = "$push";
// FOUND USER ID THEN DO REMOVE OPERATION
if (post) updateOperator = "$pull";
// QUERY
await collection('post').updateOne(
{ postId: postId },
{
[updateOperator]: {
"commentsOnPost.$[c].repliesOnPost.$[r].likesOnReply": userid
}
},
{
arrayFilters: [
{ "c.commentId": commentId },
{ "r.replyId": replyId }
]
}
)
Playground
2) Update with aggregation pipeline starting from MongoDB 4.2:
$map to iterate loop of commentsOnPost array check condition if commentId match then go to next process otherwise return existing object
$mergeObjects to merge current object with updated fields
$map to iterate loop of repliesOnPost array and check condition if replyId match then go to next process otherwise return an existing object
check condition for likesOnReply has userid then do remove using $filter otherwise insert using $concatArrays
await collection('post').findOneAndUpdate(
{ postId: "232323" },
[{
$set: {
commentsOnPost: {
$map: {
input: "$commentsOnPost",
in: {
$cond: [
{ $eq: ["$$this.commentId", commentId] },
{
$mergeObjects: [
"$$this",
{
repliesOnPost: {
$map: {
input: "$$this.repliesOnPost",
in: {
$cond: [
{ $eq: ["$$this.replyId", replyId] },
{
$mergeObjects: [
"$$this",
{
likesOnReply: {
$cond: [
{ $in: [userid, "$$this.likesOnReply"] },
{
$filter: {
input: "$$this.likesOnReply",
cond: { $ne: ["$$this", userid] }
}
},
{
$concatArrays: ["$$this.likesOnReply", [userid]]
}
]
}
}
]
},
"$$this"
]
}
}
}
}
]
},
"$$this"
]
}
}
}
}
}]
)
Playgorund
Let's say I have a Collection with a field name and only "Sally" and "Bob" names exist. I want to group my results by this value. I'm currently getting my results and then using underscore to perform the group. But I should be able to do this with an aggregate.
The following is my code
const names = ['Bob', 'Sally'];
const docs = Collection.find({name: { $in: names}}, { fields: { name:1, age:1}, sort: { name:-1 } }).fetch();
//How can I do this part in the query above
const { Bob, Sally } = _.groupBy(docs, "name");
You can try,
$match your condition
$group by name and make array called fields and push name and age
$sort by _id means name
$replaceWith to replace object in root, $arrayToObject convert k, v format to object
const names = ['Bob', 'Sally'];
const docs = Collection.aggregate([
{ $match: { name: { $in: names } } },
{
$group: {
"_id": "$name",
fields: {
$push: {
name: "$name",
age: "$age"
}
}
}
},
{ $sort: { _id: -1 } },
{
$replaceWith: {
$arrayToObject: [
[
{
k: "$_id",
v: "$fields"
}
]
]
}
}
])
Playground
You can do something like this :
db.collection.aggregate([
{
$match: {
name: {
$in: names
}
}
},
{
$group: {
"_id": "$name",
age: {
$first: "$age"
}
}
},
{
$sort: {
name: -1
}
}
])
Try it here : MongoPlayground
I need to query entries in an array, the document looks like this:
{
"_id":"5d7b4ef6f691b71b5097e9cb",
"name":"1568362230828",
"commands":[
{
"_id":"5d7b4ef6f691b71b5097e9d1",
"name":"Command - 0"
},
{
"_id":"5d7b4ef6f691b71b5097e9d0",
"name":"Command - 1"
},
{
"_id":"5d7b4ef6f691b71b5097e9cf",
"name":"Command - 2"
},
{
"_id":"5d7b4ef6f691b71b5097e9ce",
"name":"Command - 3"
},
{
"_id":"5d7b4ef6f691b71b5097e9cd",
"name":"Command - 4"
},
{
"_id":"5d7b4ef6f691b71b5097e9cc",
"name":"Command - 5"
}
],
"__v":0
}
now i want to get all commands by there id:
model.find({
commands: {
_id: ["5d7b4ef6f691b71b5097e9cf", "5d7b4ef6f691b71b5097e9cf"] }})
Pseudo query, this does not work!
How must my query looks like ?!
const schema = new mongoose.Schema({
name: String,
commands: [{
name: String
}]
});
const model = mongoose.model('Endpoints', schema);
If you want to un-filter comments in comments only document then use this query
Endpoints.find({
commands: {
$elemMatch: {
_id: { $in: ["5d7b4ef6f691b71b5097e9cf", "5d7b4ef6f691b71b5097e9cf"] }
}
}
});
If you want to filter comments in comments
Endpoints.aggregate([
{
$project: {
items: {
$filter: {
input: "$commands",
as: "item",
cond: {
$in: [
"$$item._id",
["5d7b4ef6f691b71b5097e9cf", "5d7b4ef6f691b71b5097e9cf"]
]
}
}
}
}
}
])
or without aggregation
Endpoints.find(
{ commands:
{$elemMatch: {"_id": { $in: ["5d7b4ef6f691b71b5097e9d1"]}}}
},
{ 'commands.$': 1 }
)