mongoose query array, with id array - node.js

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 }
)

Related

MongoDB: pullAll / pull whole nested array object by value using mongoose

I want to pull the whole nested array object if the object contains a specified string for a specific key. I'm using mongoose with nodejs:
DB before deletion:
{ _id : 1234
fallBackData: {
nestedKey: [ { arrayKey: "theValue" }, { arrayKey: "anotherValue" } ]
}
}
DB after deletion:
{ _id : 1234
fallBackData: {
nestedKey: [ { arrayKey: "anotherValue" } ]
}
}
I took a look at How can I pull nested object value in mongodb and $pullAll Mongo DB docs ,tried the following, but none worked:
const ad = await Ad.updateOne(
{ _id: 1234 },
{
$pullAll: {
fallbackData: { nestedKey: [{ arrayKey: "theValue"}] },
},
}
);
const ad = await Ad.updateOne(
{ _id: 1234 },
{
$pullAll: {
"fallbackData.$.nestedKey" : { arrayKey: "theValue" },
},
}
);
const ad = await Ad.updateOne(
{ _id: 1234 },
{
$pullAll: {
"fallbackData.$.nestedKey" : [{ arrayKey: "theValue"}],
},
}
);
The query return value is the following, but the object in the array is not deleted:
{
acknowledged: true,
modifiedCount: 1,
upsertedId: null,
upsertedCount: 0,
matchedCount: 1
}
You can achieve this by changing a little
playground
db.collection.update({
_id: 1234,
"fallBackData.nestedKey": {
$elemMatch: {
"arrayKey": "theValue"
}
}
},
{
"$unset": {
"fallBackData": "nestedKey"
}
})
You cannot add matching conditions with $pullAll related to Array
$pullAll expects an array of matching values to be removed
You can do $pull instead of $unset but $pull results empty array
The answer from #Gibbs removes the array completely, while you asked only to pull the specific object from it.
Here is the simple solution with $pull command:
db.collection.update({
_id: 1234,
"fallBackData.nestedKey": {
$elemMatch: {
"arrayKey": "theValue"
}
}
},
{
$pull: {
"fallBackData.nestedKey": {
arrayKey: "theValue"
}
}
})
Mongo Playground link
pullAll matches the entire object (or objects) to be pulled. In other words, if the input was:
[
{
_id: 1234,
fallBackData: {
nestedKey: [
{
arrayKey: "theValue",
foo: "bar"
},
{
arrayKey: "anotherValue",
foo: "baz"
}
]
}
}
]
Then you need to do:
db.collection.update({
_id: 1234
},
{
$pullAll: {
"fallBackData.nestedKey": [
{
arrayKey: "theValue",
foo: "bar"
}
]
}
})
See https://mongoplayground.net/p/iJkfqIWK0JO.
On the other hand, $pull can match objects based on a condition, so you can pull from the array based on the specific key you want to match. So, given the same input as above, you would simply do:
db.collection.update({
_id: 1234
},
{
$pull: {
"fallBackData.nestedKey": {
arrayKey: "theValue"
}
}
})
See https://mongoplayground.net/p/MOuSmh7Ir7b.
The conditions can be more complex than a simple field value match. For example to match and pull multiple keys:
db.collection.update({
_id: 1234
},
{
$pull: {
"fallBackData.nestedKey": {
"arrayKey": {
$in: [
"theValue",
"anotherValue"
]
}
}
}
})
See https://mongoplayground.net/p/iSMVxp7a9TX.

Filtering MongoDB document keys

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?

MongoDB (Mongoose) - Get only records where nested item is equal to a value

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!

How to add or remove a element in double nested array?

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

how to group by in mongoose

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

Resources