Finding nested array objects with mongoose - mongodb

I have a user collection like this
{
"_id": {
"$oid": "62fe22f5d37c317701922d66"
},
"name": "Fahim Faisal",
"email": "fahim.faisal.abir#gmail.cc",
"password": "$2a$12$bkjeD.GesX4f7BNVF7C0CuKkkTyQA19kv6SAWuhObmPlAB11Tz9dy",
"notes": [],
"folders": [
{
"_id": {
"$oid": "62fe22f5d37c317701922d67"
},
"name": "My Notes",
"notes": []
},
{
"_id": {
"$oid": "62fe22f5d37c317701922d68"
},
"name": "Todos",
"notes": []
},
],
"__v": 0
}
I want to check if an exact item in folders exists or not.
What I have tried so far
User.findOne(
{ _id: req.userId },
{
folders: {
$elemMatch: {
_id,
},
},
},
(err, system) => {
if (err) {
console.log(err);
} else {
console.log(system);
}
}
);
It doesn't return the exact item. It returns this
{
_id: new ObjectId("62faf0c424411076eb9b80e6"),
folders: [
{
_id: new ObjectId("6302110a230f9123c1110273"),
name: 'My Notes',
notes: []
},
{
_id: new ObjectId("6302110a230f9123c1110274"),
name: 'Todos',
notes: []
},
{
_id: new ObjectId("6302110a230f9123c1110275"),
name: 'Projects',
notes: []
},
{
_id: new ObjectId("6302110a230f9123c1110276"),
name: 'Journals',
notes: []
},
{
_id: new ObjectId("6302110a230f9123c1110277"),
name: 'Reading list',
notes: []
}
]
}
What should I do to get the exact item in the folders array exists or not?

Add the $elemMatch filter for folders in the first object parameter, and with $ (projection) to limit and return the first (matched) item of folders array.
Assume that i_d is ObjectId type.
User.findOne(
{
_id: req.userId,
folders: {
$elemMatch: {
_id: _id
}
}
},
{
"folders.$": 1
},
(err, system) => {
if (err) {
console.log(err);
} else {
console.log(system);
}
}
);
Demo # MongoPlayground

You should specify an Id in the elemMatch. For example something like this
User.findOne(
{ _id: req.userId },
{
folders: {
$elemMatch: {
_id:ObjectId('String Id'),
},
},
},
(err, system) => {
if (err) {
console.log(err);
} else {
console.log(system);
}
}
);

Related

MongoDB Aggregation - Select only same value from array inside lookup

I have aggregation like this:
Produk.aggregate([
{
$lookup: {
from: "kis_m_kategoriproduks",
localField: "idSubKategori",
foreignField: "subKategori._id",
as: "kategori",
},
},
{ $unwind: "$kategori" },
{ $sort: { produk: 1 } },
{
$project: {
_id: 0,
id: "$id",
dataKategori: {
idKategori: "$kategori._id",
kategori: "$kategori.kategori",
idSubKategori: "$idSubKategori",
subKategori: "$kategori.subKategori",
},
},
},
])
current result is :
{
"status": "success",
"data": [
{
"dataKategori": {
"idKategori": "6195bbec8ee419e6a9b8329d",
"kategori": "Kuliner",
"idSubKategori": "6195bc0f8ee419e6a9b832a2",
"subKategori": [
{
"nama": "Food",
"_id": "6195bc0f8ee419e6a9b832a2"
},
{
"nama": "Drink",
"_id": "6195bc258ee419e6a9b832a8"
}
]
}
}
]
}
I only want to display data in subKategori that the _id match with idSubKategori. this what I expected:
{
"status": "success",
"data": [
{
"dataKategori": {
"idKategori": "6195bbec8ee419e6a9b8329d",
"kategori": "Kuliner",
"idSubKategori": "6195bc0f8ee419e6a9b832a2",
"subKategori": [
{
"nama": "Food",
"_id": "6195bc0f8ee419e6a9b832a2"
}
]
}
}
]
}
here is my $kategori schema:
const schema = mongoose.Schema(
{
kategori: {
type: String,
required: true,
unique: true,
},
subKategori: [
{
id: mongoose.Types.ObjectId,
nama: String,
},
],
},
{
timestamps: false,
}
);
any suggestion?
I fix the problem by add $filter inside $project like this:
dataKategori: {
idKategori: "$kategori._id",
kategori: "$kategori.kategori",
subKategori: {
$arrayElemAt: [
{
$filter: {
input: "$kategori.subKategori",
as: "sub",
cond: { $eq: ["$$sub._id", "$idSubKategori"] },
},
},
0,
],
},
},
reference: https://stackoverflow.com/a/42490320/6412375

Return the result of an aggregate as an object not an array

I have models in my collection this way..
{
"_id": {
"$oid": "5f213786d0cdd10c61969fc7"
},
"stationStatus": "Online",
"metadata": {
"SerialNumber": "BP001",
"imsi": "082943327103528941"
},
"boxIdentity": "BP001",
"__v": 0
}
{
"_id": {
"$oid": "5f213786d0cdd10c61969fc7"
},
"stationStatus": "Offline",
"metadata": {
"SerialNumber": "BP002",
"imsi": "082943327103528941"
},
"boxIdentity": "BP002",
"__v": 0
}
This is the aggregation i'm performing on the data.
var aggregation = [
{
$project: { __v: false },
},
{
$group: {
_id: { $toLower: "$stationStatus" },
stations: {
$push: "$$ROOT",
},
},
},
{
$group: {
_id: null,
stations: {
$push: {
k: "$_id",
v: "$stations",
},
},
},
},
{
$replaceRoot: {
newRoot: { $arrayToObject: "$stations" },
},
},
];
Model.aggregate(aggregation)
.then(function (res) {
console.log(res);
resolve(res);
})
.catch(function (err) {
reject(err);
});
Data I'm receiving in an array. What I need it the data to return only the result of the pipeline in the object, not the object inside the topmost array. How can I return the result in a way so that only the first object of the array is returned, as all that the aggregation is going to return is only a single object.
[
{
"online": [
{
"_id": "5f213786d0cdd10c61969fc7",
"stationStatus": "Online",
"connectors": [
{
"_id": "5f213786d0cdd10c61969fc8",
...
},
{
"_id": "5f213791d0cdd10c61969fcf",
...
},
{
"_id": "5f213791d0cdd10c61969fd1",
...
}
],
"metadata": {
"SerialNumber": "BP001",
"imsi": "082943327103528941"
},
"boxIdentity": "BP001"
},
{
"_id": "5f2809d7b03827069d3b5627",
"stationStatus": "Online",
"connectors": [
{
"_id": "5f213786d0ccc310c61969fc8",
...
},
{
"_id": "5f213791d0cd340c61969fcf",
...
},
{
"_id": "5f213791d0cdd10c61369fd1",
...
}
],
"metadata": {
"SerialNumber": "BP002",
"imsi": "0963433223503528941"
},
"boxIdentity": "BP002"
}
]
}
]
Aggregate will always return an array.
You could destructure the value if your using es6.
Model.aggregate(aggregation)
.then(function ([theObjectYouWant]) {
console.log(theObjectYouWant);
....

Mongoose aggregate the property of subdocument and display the result

I have a document with a subdocument (not referenced). I want to apply the aggregation on the field of the subdocument.
Schema
const MFileSchema = new Schema({
path: String,
malwareNames: [String],
title: String,
severity: String // i want to aggregate bases on this field
});
const ScanSchema = new Schema({
agent: { type: Schema.Types.ObjectId, ref: "Agent" },
completedAt: Date,
startedAt: { type: Date, default: Date.now() },
mFiles: [MFileSchema] // array of malicious files schema
});
Model
let Scan = model("Scan", ScanSchema);
Task
Find the sum of severity in all scan documents of particular agents.
// agents is an array Agents (the schema is not important to show, consider the _id)
The Aggregation Query I am using
let c = await Scan.aggregate([
{ $match: { agent: agents } },
{ $project: { "mFiles.severity": true } },
{ $group: { _id: "$mFiles.severity", count: { $sum: 1 } } }
]);
console.log(c);
Actual Output
[]
Expected Output
// The value of count in this question is arbitrary
[
{ _id: "Critical", count: 30 },
{ _id: "Moderate", count: 33 },
{ _id: "Clean", count: 500 }
]
PS: Also I would appreciate if you could suggest me the best resources to learn MongoDB aggregations
You need to use $in query operator in the $match stage, and add $unwind stage before $group stage.
db.collection.aggregate([
{
$match: {
agent: {
$in: [
"5e2c98fc3d785252ce5b5693",
"5e2c98fc3d785252ce5b5694"
]
}
}
},
{
$project: {
"mFiles.severity": true
}
},
{
$unwind: "$mFiles"
},
{
$group: {
_id: "$mFiles.severity",
count: {
$sum: 1
}
}
}
])
Playground
Sample data:
[
{
"agent": "5e2c98fc3d785252ce5b5693",
"mFiles": [
{
"title": "t1",
"severity": "Critical"
},
{
"title": "t2",
"severity": "Critical"
},
{
"title": "t3",
"severity": "Moderate"
},
{
"title": "t4",
"severity": "Clean"
}
]
},
{
"agent": "5e2c98fc3d785252ce5b5694",
"mFiles": [
{
"title": "t5",
"severity": "Critical"
},
{
"title": "t6",
"severity": "Critical"
},
{
"title": "t7",
"severity": "Moderate"
}
]
}
]
Output:
[
{
"_id": "Moderate",
"count": 2
},
{
"_id": "Critical",
"count": 4
},
{
"_id": "Clean",
"count": 1
}
]
For mongoose integration:
//agents must be an array of objectIds like this
// [ObjectId("5e2c98fc3d785252ce5b5693"), ObjectId("5e2c98fc3d785252ce5b5694")]
//or ["5e2c98fc3d785252ce5b5693","5e2c98fc3d785252ce5b5694"]
const ObjectId = require("mongoose").Types.ObjectId;
let c = await Scan.aggregate([
{
$match: {
agent: {
$in: agents
}
}
},
{
$project: {
"mFiles.severity": true
}
},
{
$unwind: "$mFiles"
},
{
$group: {
_id: "$mFiles.severity",
count: {
$sum: 1
}
}
}
]);
Best place for learning mongodb aggregation is the official docs.

Mongoose aggregate with nested collection

I have following schemas.
First: AdditionalFieldSchema
const AdditionalFieldSchema = new Schema({
names: [
{
type: Schema.Types.ObjectId,
ref: "multiLanguageContent",
required: true
}
],
...
});
Second: MultiLanguageContentSchema
const MultiLanguageContentSchema = new Schema({
value: {
type: String,
required: true
},
...
});
and I have the following mongoose aggregate query.
The goal is to fetch every employees with their additionalField attached.
const employees = await Employee.aggregate([
{
$match: {
status: "active",
$nor: [
{ email: "blablabla" },
{ email: "blobloblo" }
]
}
},
{
$lookup: {
from: AdditionalField.collection.name,
let: { af_id: "$_id" },
pipeline: [{
$match: {
$expr: {
$and: [
{ $eq: ["$ownership", "$$af_id"] },
{ $eq: ["$status", "active"] },
{ $eq: ["$functionalType", "blablabla"] }
]
}
}
}],
as: "afs"
}
},
{ $unwind: "$afs" },
{ $unwind: "$afs.names" },
{
$group: {
_id: "$_id",
email: { $first: "$email" },
afs: {
$push: {
value: "$afs.value",
names: "$afs.names"
}
}
}
}
]);
The query I run to test this aggregate function.
query TestQuery {
testQuery
{
email
afs {
names {
value
}
value
}
}
}
The result I have.
"data": {
"testQuery": [
{
"email": "blablabla#test.com"
"afs": [
{
"names": [
{
"value": "Name 1"
}
],
"value": "Value 1"
},
{
"names": [
{
"value": "Name 2"
}
],
"value": "Value 2"
},
...
]
},
...
Result is good, I have data I want.
But I would like to have a result like this below
"data": {
"testQuery": [
{
"email": "blablabla#test.com",
"afs": [
{
"names": "Name 1",
"value": "Value 1"
},
{
"names": "Name 2",
"value": "Value 2"
},
...
]
},
...
It seems like { $unwind: "$afs.names" }, is not working.
Any ideas ?
Thanks, Flo

mongodb - how to project last element of an array after an update

So I have the following query:
ListModel
.findOneAndUpdate(
{
_id: list_id
},
{
$push: {
"items": {
details: the_template
}
}
},
{
new: true,
projection: {
items: 1
}
}
)
So, this returns me the array of the updated document, but how would I get only the last element inserted?
You can use of $slice, in your case :
ListModel
.findOneAndUpdate(
{
_id: list_id,
},
{
$push: {
items: {
details: the_template,
},
},
},
{
new: true,
projection: {
items: {
$slice: -1,
},
},
},
);