Update nested array objects based on a property in MongoDB - mongodb

I have a MongoDB collection which has documents which look like this:
{
createdTimestamp: 111111111111,
items: [{
itemName: 'Name 1',
quantity: 10
}, {
itemName: 'Name 2'
quantity: 20
}]
}
Now, I want to update all documents so that itemName: 'Name 1' would be updated to itemName: 'New Name'.
After an update, the above document should look like as below:
{
createdTimestamp: 111111111111,
items: [{
itemName: 'New Name',
quantity: 10
}, {
itemName: 'Name 2'
quantity: 20
}]
}
Is there any way to do this, without iterating over all documents myself?

You need to use $ positional operator to update an array element and with multi: true option you can update multiple document with the same match
db.collection.update(
{ 'items': { '$elemMatch': { 'itemName': 'Name 1' }}},
{ '$set': { 'items.$.itemName': 'New Name' }},
{ 'multi': true }
)
and with the mongodb 3.6 arrayFilters
db.collection.update(
{ 'items': { '$elemMatch': { 'itemName': 'Name 1' }}},
{ '$set': { 'items.$[item].itemName': 'New Name' }},
{ 'arrayFilter': [{ 'item.itemName': 'Name 1' }], 'multi': true }
)

you can use mongoDb arrayFilters
db.collection.update(
{ },
{ "$set": { "items.$[elem].itemName": 'new Name' } },
{ "arrayFilters": [{ "elem.itemName": 'Name 1' }], "multi": true }
)

Related

Boost final score after should clause in mongodb atlas search

I have few fields in which I want to search for users like: username, fullname, about, desgination.
Apart from this some of the users are marked as { type: 'EXPERT' }
Now my query looks like this:
const docs = await this.userModel
.aggregate()
.search({
index: 'User',
count: { type: 'total' },
compound: {
should: [
{
text: {
query: searchQuery,
path: 'fullname',
fuzzy: { prefixLength: 2 },
score: { boost: { value: 5 } },
},
},
{
text: {
query: searchQuery,
path: 'username',
fuzzy: { prefixLength: 2 },
score: { boost: { value: 3 } },
},
},
{
text: {
query: searchQuery,
path: 'designation',
fuzzy: { prefixLength: 2 },
score: { boost: { value: 5 } },
},
},
{
text: {
query: searchQuery,
path: 'about',
fuzzy: { prefixLength: 2 },
score: { boost: { value: 1 } },
},
},
{
text: {
query: 'EXPERT',
path: 'type',
score: { boost: { value: 5 } },
},
},
],
},
})
Now this works fine until I search something rubbish like 'XYZ' and the { type: "EXPERT" } still comes on top.
What I want instead is to search in the fields: username, fullname, designation, about. And the searchQuery must match atleast one of the fields and if does match then apply the boost in the final results if any of the user in final results is of type 'EXPERT'

Retrieve nested MongoDB object after querying nested object

Given the following MongoDB collection groups:
[{
_id: 'g1',
name: 'Group 1',
projects: [{
_id: 'p1',
name: 'Project 1',
tasks: [{
_id: 't1',
name: 'Task 1'
}, {
_id: 't2',
name: 'Task 2'
}]
}]
}]
I need to find task with _id = t2 and return the whole task object:
{
_id: 't2',
name: 'Task 2'
}
I'm able to find the group, either using mongodb:
db.groups.find({ 'projects.tasks._id': ObjectId('t2') })
Or with Mongoose:
Groups.findOne({ 'projects.tasks._id': 't2' })
But of course, this returns the entire group document, and not the task.
I've tried:
db.groups.aggregate([
{
$match: { 'projects.tasks._id': ObjectId('t2') }
}, {
$replaceRoot: {
newRoot: '$chapters.sections'
}
}
])
But this throws an error.

Algolia retrieve results by multiple facets

First of all, I am using Algolia JavaScript API Client V3 (Deprecated)
I have the following records
{
category: SEDAN,
manufacturer: Volkswagen,
id: '123'
},
{
category: COUPE,
manufacturer: Renault,
id: '234'
},
{
category: SEDAN,
manufacturer: Fiat,
id: '345'
},
{
category: COUPE,
manufacturer: Peugeot,
id: '456'
},
{
category: SUV,
manufacturer: Volkswagen,
id: '567'
}
I want to query Algolia and get something similar to the following json
{
categories: {
SEDAN: {
count: 2
items: [{
Volkswagen: {
count 1,
items: [{
id: '123'
}]
}
},
{
Fiat: {
count 1,
items: [{
id: '345'
}]
}
}]
},
COUPE: {
count: 2
items: [{
Renault: {
count 1,
items: [{
id: '234'
}]
}
},
{
Peugeot: {
count 1,
items: [{
id: '456'
}]
}
}]
},
SUV: {
count: 1,
items: [{
Volkswagen: {
count 1,
items: [{
id: '567'
}]
}
}]
}
}
}
I have been trying to query Algolia
index
.search({
query: '',
facets: ['category', 'manufacturer'],
attributesToRetrieve: []
})
.then((result) => {
console.log(result.facets);
});
But I am not sure if it is possible to combine the facets
facets added to a query doesn't work that way. It will simply return the record count for each facet value, not the actual records (https://www.algolia.com/doc/api-reference/api-parameters/facets/)
You can create filters around facets and use those to display results by facet value, but there isn't a way to build a single response JSON that is already grouped by facets like you show above. https://www.algolia.com/doc/api-reference/api-parameters/filters/

Aggregate data and populate in one request

I am a bit puzzled by populate in MongoDB.
I've got a Schema:
import { Schema, Document, model } from "mongoose";
export interface ProductGroupType {
id: Schema.Types.ObjectId,
title: String,
name: String,
description: String,
}
const ProductGroupSchema: Schema<Document<ProductGroupType>> = new Schema({
title: { type: String, trim: true },
name: { type: String, trim: true },
description: { type: String, trim: true },
}, { collection: "productGroups", timestamps: true });
export const ProductGroupModel = model('ProductGroup', ProductGroupSchema);
and products
import { Schema, Document, model } from "mongoose";
import { plugin as autocomplete } from 'mongoose-auto-increment';
const ProductSchema: Schema<Document<IProduct>> = new Schema({
article: Number,
name: String,
category: { type: Schema.Types.ObjectId, ref: 'ProductCategory' },
group: { type: Schema.Types.ObjectId, ref: 'ProductGroup' },
price: { type: Number, default: 0 },
discount: { type: Number, default: 0 },
stock: {
available: { type: Number, default: 0 },
reserved: { type: Number, default: 0 },
},
images: [Object],
description: String,
productDetails: Object,
}, { collection: "products", timestamps: true });
ProductSchema.plugin(autocomplete, {
model: 'Product',
field: 'article',
startAt: 10000,
});
export const ProductModel = model('Product', ProductSchema);
I need to make a request and group on the MongoDB side data by the field 'group'.
I can make this like this:
await ProductModel.aggregate([
{ $match: { category: Types.ObjectId(queryCategory.id) } },
{
$group: {
_id: '$group',
products: {
$push: {
id: '$_id',
name: '$name',
article: '$article',
price: '$price',
discount: '$discount',
description: '$description',
group: '$groupName',
}
},
count: { $sum: 1 },
}
},
]);
but the output here is:
[
{ _id: 61969583ad32e113f87d0e99, products: [ [Object] ], count: 1 },
{
_id: 61993fff452631090bfff750,
products: [ [Object], [Object] ],
count: 2
}
]
almost what I need but I've been playing around with population and I cannot make it work with Aggregation framework.
I already tried to use the 'lookup' operator but it returns an empty array and doesn't want to work.
That's how I wanted to make it work:
const products: Array<IProduct> = await ProductModel.aggregate([
{ $match: { category: Types.ObjectId(queryCategory.id) } },
{
$group: {
_id: '$group',
products: {
$push: {
id: '$_id',
name: '$name',
article: '$article',
price: '$price',
discount: '$discount',
description: '$description',
group: '$groupName',
}
},
count: { $sum: 1 },
}
},
{
$lookup: {
"from": "productGroups",
"localField": "group",
"foreignField": "_id",
"as": "groupName"
},
},
]);
Is it possible to get the same result as I've got now but populate in the same query group field?
So far the only way I've managed to populate it like this as the second request:
await ProductGroupModel.populate( products.map( (product: any) => {
return {
_id: new ProductGroupModel(product),
products: product.products,
count: product.count,
}
} ), { "path": "_id" } )
In a MongoDB aggregation pipeline, the $group stage passes along only those field explicitly declared in the stage.
In the same pipeline you show, the documents passed along by the $group stage would contain the fields:
_id
products
count
When the exector arrives a the $lookup stage, none of the documents contain a field named group.
However, the value previously contained in the group field still exists, in the _id field.
In the $lookup stage, use
"localField": "_id",
to find documents based on that value.

How to update Object's array's objects' value?

Following code is not updating book title, how can achieve my goal of updating book title?
user: {
_id: "123",
books: [{ title: "ABC", pages: 99 }],
}
await model.updateOne(
{
_id: userID,
"books._id": bookID,
},
{ book: { title: "" } }
);
From your scenario, you need arrayFilters.
db.collection.update({
_id: "123" //userID
},
{
$set: {
"books.$[book].title": ""
}
},
{
arrayFilters: [
{
"book._id": "1" //bookID
}
]
})
Sample Mongo Playground
References
How the arrayFilters Parameter Works in MongoDB
try this
await model.updateOne(
{
_id: userID,
"books._id": bookID,
},
{ title: "" }
);