Updating several objects in array of objects mongoose - node.js

I have schema like :
const UserSchema = new mongoose.Schema({
skills: [{
_id: false,
name: String,
level: Number,
exp: Number
}]
});
As example document is like :
{
"_id": {
"$oid": "5b0941419703f80a121c44df"
},
"skills": [
{
"name": "woodcutting",
"exp": 1110,
"level": 1
},
{
"name": "smithing",
"level": 0,
"exp": 0
},
{
"name": "reading",
"level": 0,
"exp": 0
},
{
"name": "writing",
"level": 0,
"exp": 0
}
]
}
So I just need to update exp and level of woodcutting and smithing in one query, is it possible, if possible, how?
Or will I need to change all array and set(replace) with it value of skills?

You can use arrayFilters from mongodb 3.6
db.collection.update(
{ },
{ "$set": {
"skills.$[skill1].exp": "your_value",
"skills.$[skill2].level": "your_value",
"skills.$[skill2].exp": "your_value",
"skills.$[skill1].level": "your_value"
}},
{ "arrayFilters": [{ "skill1.name": "woodcutting", "skill2.name": "smithing" }] }
)

Related

Mongoose aggregate by date and values

I cannot solve a problem I am having on a project.
First of all, I have a Schema called Emotions, shown below:
import mongoose from 'mongoose';
const EmotionSchema = new mongoose.Schema({
classification: {
type: String,
required: true,
},
probability: {
type: Number,
required: true,
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
}
}, {
timestamps: true,
});
export default mongoose.model('Emotion', EmotionSchema);
And some data like this:
[
{
"_id": "61144a393c532f066725bd24",
"classification": "Feliz",
"probability": 0.98,
"user": "60eecfeba0810013e750cdbc",
"createdAt": "2021-08-11T22:07:53.331Z",
"updatedAt": "2021-08-11T22:07:53.331Z",
"__v": 0
},
{
"_id": "61144a46d30bd006fa2be702",
"classification": "Feliz",
"probability": 0.98,
"user": "60eecfeba0810013e750cdbc",
"createdAt": "2021-08-11T22:08:06.618Z",
"updatedAt": "2021-08-11T22:08:06.618Z",
"__v": 0
},
{
"_id": "611541dd62a7f214afab3ceb",
"classification": "Triste",
"probability": 0.9,
"user": "60eecfeba0810013e750cdbc",
"createdAt": "2021-08-12T15:44:29.150Z",
"updatedAt": "2021-08-12T15:44:29.150Z",
"__v": 0
},
{
"_id": "611541f762a7f214afab3cf5",
"classification": "Raiva",
"probability": 0.86,
"user": "60eecfeba0810013e750cdbc",
"createdAt": "2021-08-12T15:44:55.909Z",
"updatedAt": "2021-08-12T15:44:55.909Z",
"__v": 0
},
{
"_id": "6115420362a7f214afab3cf7",
"classification": "Neutro",
"probability": 0.99,
"user": "60eecfeba0810013e750cdbc",
"createdAt": "2021-08-12T15:45:07.297Z",
"updatedAt": "2021-08-12T15:45:07.297Z",
"__v": 0
},
{
"_id": "6115420462a7f214afab3cf9",
"classification": "Neutro",
"probability": 0.99,
"user": "60eecfeba0810013e750cdbc",
"createdAt": "2021-08-12T15:45:08.002Z",
"updatedAt": "2021-08-12T15:45:08.002Z",
"__v": 0
},
{
"_id": "611543d252be9a187c380e5d",
"classification": "Feliz",
"probability": 0.91,
"user": "60eecfeba0810013e750cdbc",
"createdAt": "2021-08-12T15:52:50.599Z",
"updatedAt": "2021-08-12T15:52:50.599Z",
"__v": 0
}
]
What I want is:
First group each of these records by day.
Afterwards, count the values ​​by classification, but separating these classifications.
Something like that:
[
{
"_id": "12/08/2021",
"feliz": 1,
"triste": 1,
"raiva": 1,
"neutro": 2,
"surpreso": 0,
},
{
"_id": "11/08/2021",
"feliz": 2,
"triste": 0,
"raiva": 0,
"neutro": 0,
"surpreso": 0,
}
]
I did something like that, but it's only working for a value, eg "Feliz".
In code:
Emotion.aggregate([
{ $match:
{
user: user._id,
classification: "Feliz"
}
},
{ $group: {
_id : { $dateToString: { format: "%d/%m/%Y", date: "$createdAt" } },
feliz: { $sum: 1 }
}},
], function(err, results) {
if (err) throw err;
return res.json(results);
})
And it returns:
[
{
"_id": "12/08/2021",
"feliz": 1
},
{
"_id": "11/08/2021",
"feliz": 4
}
]
One more question:
From the Client-side, I always get values ​​for "classification" like:
"Feliz", "Triste", "Surpreso", "Raiva", "Neutro".
So is it better to add an enum to my schema "Emotion"?
(Sorry for my English, i hope you understand).
$group by createdAt date and classification, count sum
$group by createdAt only and construct the array of classification and count in key-value pair
$arrayToObject to convert above key-value pair array of object to an object format
Emotion.aggregate([
{
$match: {
user: user._id,
classification: "Feliz"
}
},
{
$group: {
_id: {
createdAt: {
$dateToString: {
format: "%d/%m/%Y",
date: "$createdAt"
}
},
classification: "$classification"
},
count: { $sum: 1 }
}
},
{
$group: {
_id: "$_id.createdAt",
classifications: {
$push: {
k: "$_id.classification",
v: "$count"
}
}
}
},
{
$addFields: {
classifications: {
$arrayToObject: "$classifications"
}
}
}
],
function(err, results) {
if (err) throw err;
return res.json(results);
})
Playground
Note: this will not return classification when count is 0! You need to do this after query in js.
One more question: From the Client-side, I always get values ​​for "classification" like: "Feliz", "Triste", "Surpreso", "Raiva", "Neutro". So is it better to add an enum to my schema "Emotion"?
It is up to your project requirement, you can set these values in enum to restrict the input.

Mongoose how to find object in array

i have tried a few method but it doesn't work. How to find object from 2 array. My attempt:
Message.find({$and : [{_id : id}, {"users._id" : userID}]})
My Message Schema
const contentSchema = new Schema({
ismy : Boolean,
message : String
},{ timestamps: true });
const receiverSchema = new Schema({
_id : String,
messages : [contentSchema]
});
const messageSchema = new Schema({
_id : String,
users : [receiverSchema]
});
module.exports = mongoose.model('Message',messageSchema);
Returned All document but i want document that is with "_id": "5f38f3574644e92d0cbbc5be",
[
{
"_id": "20860f7806d19d7a29a8addc525f73b5",
"users": [
{
"messages": [
{
"_id": "5f38f3574644e92d0cbbc5be",
"ismy": true,
"message": "aaaa",
"createdAt": "2020-08-16T08:50:31.317Z",
"updatedAt": "2020-08-16T08:50:31.317Z"
},
{
"_id": "5f38f39d4a11553f38da0ec8",
"ismy": true,
"message": "aaaa",
"createdAt": "2020-08-16T08:51:41.093Z",
"updatedAt": "2020-08-16T08:51:41.093Z"
},
{
"_id": "5f38f3a84a11553f38da0eca",
"ismy": true,
"message": "aaaaDASDASDSA",
"createdAt": "2020-08-16T08:51:52.407Z",
"updatedAt": "2020-08-16T08:51:52.407Z"
},
{
"_id": "5f38f3f3cb7eec3ffc61c050",
"ismy": true,
"message": "aaaaDASDASDSA",
"createdAt": "2020-08-16T08:53:07.809Z",
"updatedAt": "2020-08-16T08:53:07.809Z"
},
{
"_id": "5f38f3ffcb7eec3ffc61c053",
"ismy": true,
"message": "hahaha",
"createdAt": "2020-08-16T08:53:19.781Z",
"updatedAt": "2020-08-16T08:53:19.781Z"
}
],
"_id": "9471e1b040fa962df160903066e75778"
},
{
"messages": [],
"_id": "11111111111"
}
],
"__v": 4
}
]
There might be easier ways to do this, but what should work is the following aggregation-pipeline:
const ObjectId = require('mongoose').Types.ObjectId;
const res = await Message.aggregate(Message.aggregate([
{
"$match": {
// set the document id here
_id: id
}
},
{
"$project": {
"filteredMessages": {
"$filter": {
"input": {
"$map": {
"input": "$users",
"as": "user",
"in": {
"messages": {
"$filter": {
"input": "$$user.messages",
"as": "msg",
"cond": {
// need to use ObjectId here, as the messages id's won't be plain string
"$eq": ["$$msg._id", ObjectId("5f38f3574644e92d0cbbc5be")]
}
}
}
}
},
},
"as": "filteredUser",
"cond": {
"$and": [
{"$gt": [{"$size": "$$filteredUser.messages"}, 0]}
]
}
}
}
}
}
]);
What I'm doing here is applying $filter and $map - $map is needed because the inner messages array can change as a result of the filtering, and the users array of course does not match the conditions when the messages-array was changed.

How to query from two collections in mongodb using aggregate?

I have the two following collections in my mongoDB:
PRODUCTS:
[
{
"_id": "5ebb0984e95e3e9e35aab3bf",
"name": "Product 1",
"brand": "ABC",
"code": "7891910000190",
"description": "Lorem Ipsum"
},
{
"_id": "5ebdb9f3d943ae000a4a9714",
"name": "Product 2",
"brand": "DEF",
"code": "7891910000190",
"description": "Lorem Ipsum"
}
]
STOCK GOODS:
[
{
"_id": "5ed0586b7f2cfe02387d0706",
"companyId": "5ed0491bf9a892a5fd9b4d9b",
"branchId": "5ed049a8f9a892a5fd9b4d9e",
"inventoryId": "5ed04d01e9e43cee79734b95",
"productId": "5ebb0984e95e3e9e35aab3bf"
},
{
"_id": "5ed058da75e4e01013f5779d",
"companyId": "5ed0491bf9a892a5fd9b4d9b",
"branchId": "5ed049a8f9a892a5fd9b4d9e",
"inventoryId": "5ed04cede9e43cee79734b93",
"productId": "5ebb0984e95e3e9e35aab3bf"
},
{
"_id": "5ed059483da3bafccb34cec3",
"companyId": "5ed0491bf9a892a5fd9b4d9b",
"branchId": "5ed049a8f9a892a5fd9b4d9e",
"inventoryId": "5ed04d01e9e43cee79734b95",
"productId": "5ebb0984e95e3e9e35aab3bf"
}
]
I want to get the follow result:
[
{
"_id": "5ed0586b7f2cfe02387d0706",
"data": {
"_id": "5ebb0984e95e3e9e35aab3bf",
"brand": "ABC",
"code": "7891910000190",
"description": "Lorem Ipsum",
"name": "Product 1",
}
},
{
"_id": "5ed059483da3bafccb34cec3",
"data": {
"_id": "5ebb0984e95e3e9e35aab3bf",
"brand": "ABC",
"code": "7891910000190",
"description": "Lorem Ipsum",
"name": "Product 1",
}
}
]
My NodeJS and MongoDB aggregate look like this:
const mongoose = require("mongoose");
const StockGoods = mongoose.model("StockGoods");
ObjectId = require("mongodb").ObjectID;
exports.getProducts = async (companyId, branchId, inventoryId) => {
const response = await StockGoods.aggregate([
{
$match: {
companyId: new ObjectId("5ed0491bf9a892a5fd9b4d9b"), // new ObjectId(companyId)
inventoryId: new ObjectId("5ed04d01e9e43cee79734b95"), // new ObjectId(inventoryId)
branchId: new ObjectId("5ed049a8f9a892a5fd9b4d9e"), // new ObjectId(branchId)
},
},
{
$lookup: {
from: "products",
localField: "productId",
foreignField: "_id",
as: "inventory_docs",
},
},
{
$project: {
data: "$inventory_docs",
},
},
{ $unwind: "$data" },
]);
return response;
};
This is the same database and the aggregate function described above, you can check this: https://mongoplayground.net/p/FRgAIfO2bwh.
But, this function aggregate does not working when I use nodejs.
My API returns nothing (empty array).
Whats wrong?
Issue here is the way you have stored data in collection.... StockGoods collection has companyId, inventoryId and branchIdas a string but aggregation is looking for ObjectId aee below match criteria:
$match: {
companyId: new ObjectId("5ed0491bf9a892a5fd9b4d9b"), // new ObjectId(companyId)
inventoryId: new ObjectId("5ed04d01e9e43cee79734b95"), // new
ObjectId(inventoryId)
branchId: new ObjectId("5ed049a8f9a892a5fd9b4d9e"), // new ObjectId(branchId)
},
Based on the data you store in DB either you need to update match with string.
So below Aggregation will work for you..
[
{
"$match": {
"companyId": "5ed0491bf9a892a5fd9b4d9b",
"inventoryId": "5ed04d01e9e43cee79734b95",
"branchId": "5ed049a8f9a892a5fd9b4d9e"
}
},
{
"$lookup": {
"from": "products",
"localField": "productId",
"foreignField": "_id",
"as": "inventory_docs"
}
},
{
"$project": {
"data": "$inventory_docs"
}
},
{
"$unwind": "$data"
}
]
This works you can test in Compass Aggregator tab
[RESOLVED]
The problem was in the scheme:
companyId: {
type: String,
required: true,
},
The correct is:
companyId: {
type: Schema.Types.ObjectId,
required: true,
},

How to lookup subquery documents with another collection moongoose

I need to join user collections two times as same user will post product and purchased user details as sub document.
Product Schema:
{
product_title: String,
product_desc: String,
Product_purchased:[
{
userid: mongoose.Schema.ObjectId,
purchased_date: Date
}]
posteduserId: mongoose.Schema.ObjectId
}
Users Schema:
{
_id: userId,
name: String,
Pic: String
}
Example document:
[
{
"product_title":"title",
"product_desc":"desc",
"Product_purchased":[
{
"userid":"5d4hvh7duc7c7c8d9scbe",
"name":"name",
"Pic":"url",
"purchased_date":"Date"
},
{
"userid":"5d4hvh7duc7c7c8d9scbe",
"name":"name",
"Pic":"url",
"puchased_date":"Date"
}
],
"posteduserId": "5d4hvh7duc7c7c8d9scbe",
"userid": "5d4hvh7duc7c7c8d9scbe",
"name": "name",
"pic": "url",
}
]
Join same user table with posted userid and subarray of purchased userIds.
Please help me how to crack this one, Thanks in advance.
First you need to fix your Product schema like this to include the ref field:
const mongoose = require("mongoose");
const ProductSchema = new mongoose.Schema({
product_title: String,
product_desc: String,
Product_purchased: [
{
userid: {
type: mongoose.Schema.ObjectId,
ref: "User"
},
purchased_date: Date
}
],
posteduserId: {
type: mongoose.Schema.ObjectId,
ref: "User"
}
});
module.exports = mongoose.model("Product", ProductSchema);
I setup user model like this:
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({
name: String,
Pic: String
});
module.exports = mongoose.model("User", UserSchema);
The ref User value must match the model name User in the user model.
Then you need to populate two times like this:
router.get("/products", async (req, res) => {
const result = await Product.find({})
.populate("Product_purchased.userid")
.populate("posteduserId");
res.send(result);
});
Let's say you have this 3 users:
{
"_id": "5e133deb71e32b6a68478ab4",
"name": "user1 name",
"Pic": "user1 pic",
"__v": 0
},
{
"_id": "5e133df871e32b6a68478ab5",
"name": "user2 name",
"Pic": "user2 pic",
"__v": 0
},
{
"_id": "5e133e0271e32b6a68478ab6",
"name": "user3 name",
"Pic": "user3 pic",
"__v": 0
}
And this product:
{
"_id": "5e133e9271e32b6a68478ab8",
"product_title": "product1 title",
"product_desc": "product1 description",
"Product_purchased": [
{
"purchased_date": "2020-01-06T14:02:14.029Z",
"_id": "5e133e9271e32b6a68478aba",
"userid": "5e133df871e32b6a68478ab5"
},
{
"purchased_date": "2020-01-06T14:02:14.029Z",
"_id": "5e133e9271e32b6a68478ab9",
"userid": "5e133e0271e32b6a68478ab6"
}
],
"posteduserId": "5e133deb71e32b6a68478ab4",
"__v": 0
}
The result will be:
[
{
"_id": "5e133e9271e32b6a68478ab8",
"product_title": "product1 title",
"product_desc": "product1 description",
"Product_purchased": [
{
"purchased_date": "2020-01-06T14:02:14.029Z",
"_id": "5e133e9271e32b6a68478aba",
"userid": {
"_id": "5e133df871e32b6a68478ab5",
"name": "user2 name",
"Pic": "user2 pic",
"__v": 0
}
},
{
"purchased_date": "2020-01-06T14:02:14.029Z",
"_id": "5e133e9271e32b6a68478ab9",
"userid": {
"_id": "5e133e0271e32b6a68478ab6",
"name": "user3 name",
"Pic": "user3 pic",
"__v": 0
}
}
],
"posteduserId": {
"_id": "5e133deb71e32b6a68478ab4",
"name": "user1 name",
"Pic": "user1 pic",
"__v": 0
},
"__v": 0
}
]

Returning all fields from MonoDB matched by value of a field embedded in Object type

for the following MongoDB results, i'm trying to write a query where the name filed is NOT Demo or Demo 2 the Items.location is equal to hongkong:
{
"_id": ObjectID("573ac4d1ad364cd534a03e15"),
"updatedAt": ISODate("2016-05-17T07:14:25.341Z"),
"createdAt": ISODate("2016-05-17T07:14:25.341Z"),
"name": "Testing",
"Items": {
"date": "18052016",
"location": "hongkong"
},
"__v": 0
},
{
"_id": ObjectID("573ac4d1ad364cd534a03e16"),
"updatedAt": ISODate("2016-05-17T07:14:25.341Z"),
"createdAt": ISODate("2016-05-17T07:14:25.341Z"),
"name": "Demo",
"Items": {
"date": "18052016",
"location": "hongkong demo"
},
"__v": 0
},
{
"_id": ObjectID("573ac4d1ad364cd534a03e16"),
"updatedAt": ISODate("2016-05-17T07:14:25.341Z"),
"createdAt": ISODate("2016-05-17T07:14:25.341Z"),
"name": "Demo 2",
"Items": {
"date": "18052016",
"location": "hongkong demo"
},
"__v": 0
}
My query looks like:
mySchema.statics.testing = function *() {
var output = this.find(
{
name: {
$nin: ["Demo", "Demo 2"]
}
}
,{
Items: {
$elemMatch: { location: "hongkong" }
}
}).exec();
return output;
};
and my schema is below:
var mongoose = require('mongoose');
var mySchema = new mongoose.Schema({
name: String,
Items: Object
}, { timestamps: true });
The query seems to only return the "_id" field for the matched where as i want the all the fields returned, name, Items..etc. What am i missing?
try this code
mySchema.statics.testing = function (fn) {
this.find(
{
name: {
$nin: ["Demo", "Demo 2"]
},
"Items.location": "hongkong"
})
.exec(function (err, docs) {
fn(err, docs)
})
};
fixed. Needed:
var output = this.find(
{
name: {
$nin: ["Demo", "Demo 2"]
},
"Items.location" : "hongkong"
}).exec();
return output;
};

Resources