Iterate Through mongodb using mongoose to find nested-like data - node.js

Let's say i have 5 documents in members collection as follow:
[
{
_id: ObjectId("60e6afadfb6fe6510155e9b2"),
name: "James",
parentId: ObjectId("60e6afadfb6fe6510155e9b1")
},
{
_id: ObjectId("60e6afadfb6fe6510155e9b1"),
name: "Michael",
parentId: ObjectId("60e6afadfb6fe6510155e9b0")
},
{
_id: ObjectId("60e6afadfb6fe6510155e9b0"),
name: "Josh",
parentId: ObjectId("60e6afadfb6fe6510155e9af")
},
{
_id: ObjectId("60e6afadfb6fe6510155e9af"),
name: "Robert",
parentId: ObjectId("60e6afadfb6fe6510155e9ad")
},
{
_id: ObjectId("60e6afadfb6fe6510155e9ad"),
name: "William",
parentId: null
},
]
All I want is to iterate through those documents to get the full name of James
My output should look like 'James Michael Josh Robert William' consider that the parentId in the child document is the _id of the parent document

You can use the concept of the linked list to traverse through an array. To my knowledge, it is not possible on the DB level, and since you have mentioned the nodejs tag. So here is a nodejs solution
const {Types: {ObjectId}} = require("mongoose")
let data = [
{
_id: ObjectId("60e6afadfb6fe6510155e9b2"),
name: "James",
parentId: ObjectId("60e6afadfb6fe6510155e9b1")
},
{
_id: ObjectId("60e6afadfb6fe6510155e9b1"),
name: "Michael",
parentId: ObjectId("60e6afadfb6fe6510155e9b0")
},
{
_id: ObjectId("60e6afadfb6fe6510155e9b0"),
name: "Josh",
parentId: ObjectId("60e6afadfb6fe6510155e9af")
},
{
_id: ObjectId("60e6afadfb6fe6510155e9af"),
name: "Robert",
parentId: ObjectId("60e6afadfb6fe6510155e9ad")
},
{
_id: ObjectId("60e6afadfb6fe6510155e9ad"),
name: "William",
parentId: null
},
]
let names = []
let current = data[0]
// handle null / undefined current value
while(current) {
names.push(current.name)
current = data.find(v=>v._id.equals(current.parentId))
}
console.log(names.join(' ')) // James Michael Josh Robert William

Related

mongoose - how to query records of a collection which combine with an ObjectId-to-Number Map from another collection?

Consider the schema below:
schema/collectible.ts: The items that can be collected
const Collectible = new mongoose.schema({
title: String,
image: String
})
new Collectible({ title: 'ABC', image: 'ABC.png' }).save()
new Collectible({ title: 'DEF', image: 'DEF.png' }).save()
schema/inventory.ts: Storing a Map that represent the number of Collectible that an user owned
const Inventory = new mongoose.schema({
owner: { type: Types.ObjectId, ref: 'User' },
items: {
type: Map,
of: Number
}
})
new Inventory({
owner: ObjectId(USER_ID),
items: {
"abc_objectid_str": 2,
"def_objectid_str": 4,
}
}).save()
The goal of the query result is
[
{ title: 'ABC', image: 'ABC.png', amount: 2 },
{ title: 'DEF', image: 'DEF.png', amount: 4 },
]
One option is to use $lookup pipeline with $objectToArray and $reduce:
db.collectible.aggregate([
{$lookup: {
from: "inventory",
let: {title: {$toLower: "$title"}},
pipeline: [
{$match: {owner: ObjectId("5a934e000102030405000000")}},
{$project: {_id: 0, data: {$objectToArray: "$items"}}},
{$set: {
data: {
$reduce: {
input: "$data",
initialValue: 0,
in: {
$add: [
{$cond: [
{$eq: [{$first: {$split: ["$$this.k", "_"]}}, "$$title"]},
"$$this.v",
0
]},
"$$value"
]
}
}
}
}
}
],
as: "countData"
}
},
{$project: {_id: 0, image: 1, title: 1, amount: {$first: "$countData.data"}}}
])
See how it works on the playground example

Mongoose Aggregate - Group by field from array

For one of my project, I am working on MongoDB database and Mongoose as ODM in nestjs project.
One of my collection is looks like this data:
[
{
_id: '56cb91bdc3464f14678934ca',
organization: 'New Circle Ltd',
rooms: [
{
name: 'Alex',
workId: 'abc',
},
{
name: 'John',
workId: 'cde',
},
{
name: 'John',
workId: 'abc',
},
{
name: 'Alex',
workId: 'cdlw',
},
{
name: 'Alex',
workId: 'rps',
},
],
},
];
The requirement is to get one data by id and the data should be like the data provided bellow.
{
_id: '56cb91bdc3464f14678934ca',
organization: 'New Circle Ltd',
rooms: [
{
name: 'Alex',
workIds: ['abc', 'cdlw', 'rps'],
},
{
name: 'John',
workIds: ['cde', 'abc'],
},
],
},
Please suggest me how can I get this data

findOneAndUpdate doesn't update object's field (array of objects)

I'm trying to update multiple fields of a object in an array but it doesn't work.
what am i doing wrong?
Data Sample:
{
_id: 'mongodbid',
name: 'something',
employees: [
{
age: 25,
name: 'name',
salary: 500
},
{
age: 28,
name: 'name2',
salary: 700
}
],
}
Query:
await this.somethingModel
.findOneAndUpdate(
{
_id: id,
'employees.age': 25,
},
{
$set: {
'employees.$.salary': 600,
'employees.$.name': 'name4',
}
},
)
.exec();
I've added a arrayFilter in findOneAndUpdate. Try with this one, I hope, it works.
await this.somethingModel.findOneAndUpdate(
{
_id: id,
},
{
$set: {
"employees.$[elem].salary": 600,
"employees.$[elem].name": "name4"
}
},
{
$arrayFilters: [
{
"elem.age": 25,
},
],
});
Turns out problem wasn't with the query itself, rather the schema.
I forgot to add [ ] to the employees field in the schema.
I was using 'raw' from '#nestjs/mongoose' package, I mistakenly wrote this:
#Prop(raw({ age: Number, salary: Number, name: String }))
employees: { age: number; salary: number; name: string}[]
Instead of this:
#Prop(raw([{ age: Number, salary: Number, name: String }]))
employees: { age: number; salary: number; name: string}[]

How could I merge 2 documents into one and delete them after this step?

I have this model:
const person = new Schema({
name: String,
lastName: String,
accounts: [{
provider: String,
email: {
type: String,
lowercase: true
},
password: String,
}
]
})
Then a user registered first with Facebook and then with email, now in the database there are two documents. But now I wanna combine these two documents in one and in this way the user could log in to the same "account" but with different types of authentication.
My first option consists of finding each user and then merging, adding to the collection and then deleting the old ones. But I think this could cause problems to long-term
*update *
This is doc 1:
{
name: "John",
lastName: "Doe",
accounts: [{
provider: "google",
email: "my#email.com",
password: p4ssw0rd,
}]
}
This is doc2, this doesn't have a name and the last name because these aren't required:
{
accounts: [{
provider: "simple",
email: "my2#email.com",
password: p4ssw0rd2,
}]
}
and then the admin can merge and then have this:
{
name: "John",
lastName: "Doe",
accounts: [{
provider: "google",
email: "my#email.com",
password: p4ssw0rd,
},{
provider: "simple",
email: "my2#email.com",
password: p4ssw0rd2,
}]
}
I see it as follow:
Step 1: Update document doc1 with joined content from doc1 and doc2
var doc3=db.collection.aggregate([
{
$match: {
_id: {
$in: [
1,
2
]
}
}
},
{
$unwind: "$accounts"
},
{
$group: {
_id: 1,
name: {
$first: "$name"
},
lastName: {
$first: "$lastName"
},
accounts: {
$push: "$accounts"
}
}
}
])
db.collection.save(doc3)
playground_step_1
Step 2: Remove the doc2 ( _id:2 )
db.collection.remove({_id:2})
It is interesting to understand how you are going to correlate or understand how doc1 is related to doc2 ...

Is there a way to validate related documents in mongoose that is more efficient than .populate() and aggregate it together

Given these models:
Products:
const productSchema = new Schema({
name: String,
description: String,
price: Number,
steps: [{
type: Schema.Types.ObjectId,
ref: 'steps'
}]
});
Steps:
const stepSchema = new Schema({
name: String,
minimum: Number,
maximum: Number,
items: [
{
name: String,
price: Number
}
]
});
Is there a better way to query an object that has N number of Products and not all products.steps selected without using .populate() (because it will make a request for each value inside the products.steps array) and a for loop to sum all prices included in productSchema and stepSchema?
Exemple of use:
On my documents I've got product collection like this:
[{
id: 1
name: Pizza,
description: "Medium (3 flavors)",
price: 10,
steps: [{
"1",
"2"
}]
},
{
id: 2
name: Pizza,
description: "small (1 flavors)",
price: 10,
steps: [{
"1",
}]
}]
And steps like:
[{
id: 1
name: "Flavor",
minimum: 0,
maximum: 1,
items: [
{
id: 1
name: "mozzarella",
price: 4
},
{
id: 2
name: "pepperoni",
price: 5
}
]
},
{
id: 2
name: "Border",
minimum: 0,
maximum: 0,
items: [
{
id: 3
name: "With normal cheese",
price: 0
},
{
id: 4
name: "With special cheese",
price: 3
}
]
}]
So is there a better way to validade an object like this in my BD with less query and without a for loop?
products: [
{
productId: 1,
price: 30,
quantity: 3,
steps: [{
stepId: 1
items: [
{
itemId: 1,
price: 4
},
{
itemId: 2,
price: 5
}
]
}]
}
]
I think below steps should be followed:
$lookup(aggregation) or populate
$project(aggregation)

Resources