Mongoose check specific element array length - mongodb

how to find array length from a specific mongoose model ?
{
"_id": {
"$oid": "626cf1ccb6da65761db4b146"
},
"ID_NUM": "ID=2796414693",
"SET_ARRAY": [
"sruMkdGUeRbWZz6FSz",
"6U7ugxem7Y0/ellBUF"
],
"__v": 0
}
I did
const lens = await model.findOne({ ID_NUM: "ID=2796414693" })
then
console.log(lens.SET_ARRAY.length)
I get no error and no result.

You can use $size of mongo aggregate:
const lens = await model.findOne(
[{
$match: {
ID_NUM: "ID=2796414693",
}
},
{
$project: {
_id: 0,
ARRAY_LEN: {
$size: "$SET_ARRAY"
}
}
}
]
)

Related

mongoose findByIdAndUpdate array of object not working

I try to update array of object with mongoose methodes. When i try with vanila JS it worked but with mongoose not.
model:
const exampleSchema = new mongoose.Schema({
arrayOfObjects: [
{ name: String, id: mongoose.Schema.Types.ObjectId },
],
});
find and update by vanila js
const example = await Example.findById(req.body.propertyX);
const validIndex = example.arrayOfObjects.findIndex((v) => v.propertyY === req.body.Y);
if (validIndex === -1) {
example.arrayOfObjects.push({ propertyY: req.body.Y, propertyZ: req.body.Z });
} else {
example.arrayOfObjects[validIndex] = { propertyY: req.body.Y, propertyZ: req.body.Z };
console.log('update');
}
await recipe.save();
but when I try use findByIdAndUpdate , $set methode dont work (even $push not working...push is pushing new object id without req.body fields)
mongoose findByIdAndUpdate
const example = await Example.findByIdAndUpdate(req.body.x, {
// arrayOfObjects: { $push: { propertyY: req.body.Y, propertyX: req.body.X} },
$set: { 'arrayOfObjects.$.propertyY': req.body.Y, 'arrayOfObjects.$.propertyX': req.body.X },
});
The issue is with your understand of the positional operator $, from the docs:
the positional $ operator acts as a placeholder for the first element that matches the query document, and
This means it excepts to find a match in the array based on the query, in your case the query does not contain anything regarding the voted array, so you get the following error:
[The positional operator did not find the match needed from the query.]
So what can we do? actually doing the update you want is not so trivial, it only became possible in recent years with the introduction of pipelined updates which allow you to use aggregation operators in your update body, now we can do what you want like so:
db.collection.findByIdAndUpdate(req.body.postId,
[
{
$set: {
voted: {
$ifNull: [
"$voted",
[]
]
}
}
},
{
$set: {
voted: {
$concatArrays: [
{
$filter: {
input: "$voted",
cond: {
$ne: [
"$$this.voterId",
req.body.userId
]
}
}
},
[
{
$mergeObjects: [
{
$ifNull: [
{
$arrayElemAt: [
{
$filter: {
input: "$voted",
cond: {
$eq: [
"$$this.voterId",
req.body.userId
]
}
}
},
0
]
},
{}
]
},
{
voteRank: req.body.rank,
voterId: req.body.userId
}
]
}
]
]
}
}
}
])
Mongo Playground
You can drop the $mergeObjects operator if you don't need it, I added it incase the object could have additional properties that you want to preserve throughout an update. but probably not the case.
It then simplifies the code a little:
db.collection.findByIdAndUpdate(req.body.postId,
[
{
$set: {
voted: {
$ifNull: [
'$voted',
[],
],
},
},
},
{
$set: {
voted: {
$concatArrays: [
{
$filter: {
input: '$voted',
cond: {
$ne: [
'$$this.voterId',
req.body.userId,
],
},
},
},
[
{
voteRank: req.body.rank,
voterId: req.body.userId
}
],
],
},
},
},
]);

Mongodb find document in collection from field in another collection

I have two collections: Sharing and Material.
Sharing:
{
from_id: 2
to_id: 1
material_id: material1
}
Material:
{
_id: material1
organization_id: 2
},
{
_id: material2
organization_id: 1
},
{
_id: material3
organization_id: 1
},
--Edit:
There are three materials, 2 belong to organization_id(1) and 1 belongs to organization_id(2). The organization_id does not match 1 in material1 (and instead belongs to material2), but in the Sharing collection, the to_id does match 1. If the match exists, I'd like to find the Material document _id which is equal to the material_id of Sharing AND find the Material documents where the organization_id is equal to 1.
I'd like to check if a field in Sharing (to_id) has a value that is equal to a field in Material (organization_id) AND check if organization_id is equal to 1. If there is a document that exists from this, do another check to find whether the _id of Material is equal to the material_id of Sharing and return all documents & the total count.
If there is no equal value, I'd like to omit that result and send the object with only organization_id equal to 1 and get the total count of this result.
Right now, I do it in a very inefficient way using .map() to find this. Below is my code:
export const getMaterials = async (req, res) => {
const sharing = await Sharing.find({to_id: 1});
let doneLoad;
try {
if (sharing && sharing.length>0) {
const sharingTotal = await Material.find( {$or: [ {organization_id: 1}, {_id: sharing.map((item) => item.material_id)} ] } ).countDocuments();
const sharingMats = await Material.find( {$or: [ {organization_id: 1}, {_id: sharing.map((item) => item.material_id)} ] } );
res.status(200).json({data: sharingMats});
doneLoad= true;
}
else if (!doneLoad) {
const materialTotal = await Material.find({organization_id: 1}).countDocuments();
const materials = await Material.find({organization_id: 1});
res.status(200).json({data: materials});
}
} catch (error) {
res.status(404).json({ message: error.message });
}
}
I have tried using aggregation to get my desired result but I cannot find any solution that fits my requirements. Any help would be great as I am quite new to using mongodb. Thanks.
Edit (desired result):
Materials: [
{
_id: material1,
organization_id: 1
},
{
_id: material2,
organization_id: 1
},
{
_id: material3,
organization_id: 1
}
]
You can use sub-pipeline in a $lookup to perform the filtering. $addFields the count using $size later.
db.Sharing.aggregate([
{
"$match": {
to_id: 1
}
},
{
"$lookup": {
"from": "Material",
"let": {
to_id: "$to_id",
material_id: "$material_id"
},
"pipeline": [
{
"$match": {
$expr: {
$or: [
{
$eq: [
"$$to_id",
"$organization_id"
]
},
{
$eq: [
"$$material_id",
"$_id"
]
}
]
}
}
},
{
"$addFields": {
"organization_id": 1
}
}
],
"as": "materialLookup"
}
},
{
"$addFields": {
"materialCount": {
$size: "$materialLookup"
}
}
}
])
Here is the Mongo playground for your reference.

Delete document that has size greater than a specific value

I have a collection which contains a multiple documents whose size has increased from 16MBs or is about to reach 16MBs.
I want query that finds documents which have size greater than 10MBs and delete all of them.
I am using following to find the size of document.
Object.bsonsize(db.test.findOne({type:"auto"}))
Is there a way to embed this query inside db.test.deleteMany() query?
This following query deletes the documents with size greater than the specified size (the size is specified in bytes). This query is valid with MongoDB v4.4 or higher.
db.collection.deleteMany( {
$expr: { $gt: [ { $bsonSize: "$$ROOT" }, SIZE_LIMIT ] },
type: "auto"
} )
The following script runs for MongoDB v4.2 or earlier:
const SIZE_LIMIT = 75 // substitute your value here in bytes
let idsToDelete = [ ]
let crsr = db.collection.find()
while(crsr.hasNext()) {
let doc= crsr.next()
if (Object.bsonsize(doc) > SIZE_LIMIT) {
idsToDelete.push(doc._id)
}
}
db.collection.deleteMany( { _id: { $in: idsToDelete } } )
I think you have to do it like this:
db.test.aggregate([
{ $match: { type: "auto" } },
{ $project: { bsonSize: { $bsonSize: "$$ROOT" } } },
{ $match: { bsonSize: { $gt: 16e6 } } },
]).forEach(function (doc) {
db.test.deleteOne({ _id: doc._id });
})
Or if you prefer deleteMany:
var ids = db.test.aggregate([
{ $match: { type: "auto" } },
{ $project: { bsonSize: { $bsonSize: "$$ROOT" } } },
{ $match: { bsonSize: { $lt: 16e6 } } }
]).toArray().map(x => x._id);
db.test.deleteMany({ _id: { $in: ids } });

Mongodb aggregation pass argument to element size of $sample

Hello every body here any one can help me with query below
I want to get quiz list with random amount
the amount of rendom will
base on each lesson
The problem is
mongodb not allow to pass argument to element size of $sample
Any one can give me the solution
lessonModel.aggregate([
{ $match : {'quiz.status':1 } },
{
$lookup : {
from : 'quiz',
let : { 'lesson_id' : '$_id','limit' : '$quiz.amount' },
pipeline : [
{
$match: {
$expr: {
$eq: [ "$lesson_id", "$$lesson_id" ]
}
}
},
{
$project: {
title:1,
check_list:1,
duration:1
}
},
{ $sample: { size: '$$limit' } }
],
as: 'quiz'
}
},
{$unwind: '$quiz'},
{ $replaceRoot: { newRoot: "$quiz" } }
]).exec();
The error said size argument to $sample must be a number
Here is my sample data
UPDATE
I think your main problem is to randomly pick amount number of quizs under each lesson. Since $sample is not helpful use $function (New in version MongoDB 4.4).
Solution
Inside $function operator write some logic to
Shuffle the questions (You can change it to your requirement).
Slice it to return the number(amount) of questions required.
db.lessons.aggregate([
{ $match: { "quiz.status": 1 } },
{
$lookup: {
from: "quiz",
let: { "lesson_id": "$_id" },
pipeline: [
{
$match: {
$expr: { $eq: ["$lesson_id", "$$lesson_id"] }
}
},
{
$project: {
title: 1,
check_list: 1,
duration: 1
}
}
],
as: "questions"
}
},
{
$project: {
quiz: {
$function: {
body: function(questions, amount) {
if (amount == 0) return [];
for (let i = questions.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[questions[i], questions[j]] = [questions[j], questions[i]];
}
return questions.slice(0, amount);
},
args: ["$questions", { $ifNull: ["$quiz.amount", 0] }],
lang: "js"
}
}
}
},
{ $unwind: "$quiz" },
{ $replaceRoot: { newRoot: "$quiz" } }
]);
$sample does not support variables. A number must be specified explicitly like:
{
$sample: { size: 1 }
}
Also replace your let as shown below because last lesson document has no amount filed in the quiz object
let: {
'lesson_id': '$_id',
'limit': { $ifNull: ['$quiz.amount', 0] } // or any other number.
},
Wrong:
{
$sample: { size: "$$limit" } // Wont work!
}

Mongo result set with multiple memberIds

I am using Meteor/Mongo with Typescript/Javascript.
I have a chat app I am developing. I get a result set from Mongo.
const chats: Mongo.Cursor<Chat> = Chats.find(
{ memberIds: 'J65'},
{
sort: { lastMessageCreatedAt: -1 },
transform: this.transformChat.bind(this),
fields: { memberIds: 1, lastMessageCreatedAt: 1 }
}
);
This returns the Chats for J65 .
Question
Is it possible to have multiple memberIds? i.e. return the results for more than one matching id
More info
I have tried this with no success, i.e. it returns nothing even though there is a match (no errors though).
let registeredIds: String[] = ['J65', 'J66'];
...
{ memberIds: registeredIds },
this is what is in the database:
[
{
"_id": "CHb8FSuGSfZMPhkrW",
"memberIds": [
"P9",
"J64"
]
},
{
"_id": "uMSJjHheTp7RhGdH3",
"memberIds": [
"P9",
"J65"
]
},
{
"_id": "e6ZMmRaJLPptF63z8",
"memberIds": [
"P9",
"J66"
]
}
]
You should use the $in operator.
Your code shall look like:
const chats: Mongo.Cursor<Chat> = Chats.find(
{ memberIds: {$in:['J65','J66','J67']},
{
sort: { lastMessageCreatedAt: -1 },
transform: this.transformChat.bind(this),
fields: { memberIds: 1, lastMessageCreatedAt: 1 }
}
);
Refer docs for more information.