How to find out number of elements in MongoDB array? - database

My collection of products consists of _id, product/title, product/price and reviews. Reviews is an array with all reviews for that specific product. I am trying to print out 10 products with the largest number of reviews but can't find the right solution.
This is what I tried so far:
var mapFunction = function() {
emit(this._id, {
product: this,
count_reviews: this.reviews.length
});
};
var reduceFunction = function(key, values) {
var count = 0;
var product;
values.forEach(function(value) {
count += value.count_reviews;
product = value.product
});
return { count_reviews: count, product: product};
};
db.products.mapReduce(mapFunction, reduceFunction, {
out: { inline: 1},
query: {},
sort: { count_reviews: -1 },
limit: 10
});
When I run this code it prints out 10 elements, but not with the largest number of reviews.

I wouldn't use mapReduce (unless you're on a version so old you have to)
I'd use an aggregation, if you have strict data - you can omit the $match, if you have "strictish" data (e.g., it's an array or missing) you can use a simpler match: { reviews: { $exists: true } }
db.products.aggregate([
{
$match: {
"value.reviews": { $type: "array" }
}
},
{
$set: { review_count: { $size: "$value.reviews" } }
},
{
$sort: { review_count: -1 },
},
{
$limit: 10
}
])

Related

ES6 : Create objects in a given format from array of key values

var result = {445: "L005.0", 455: "L006.0", 456: "L007.0", 457: "L008.0", 458: "L009.0", 459: "L027.0", 467: "L005.7", 580: "L001.0", 581: "L002.0", 587: "L003.0"};
From this "result", I want to output an object like this
{
"445": {
name: result[445],
icon: "fa-search"
},
"455": {
name: result[455],
icon: "fa-search"
},
"456": { ... },
"457": { ... },
...
...
}
So you need to iterate over keys to construct new object o;
let res={}; //initializing
for(let i of Object.keys(result))
{
res[i]={
"name": result[i],
"icon":"fa-seach"
}
}
console.log(res) //to test

Mongoose Pull objects by their property values from an array (possibly empty) [duplicate]

Here is array structure
contact: {
phone: [
{
number: "+1786543589455",
place: "New Jersey",
createdAt: ""
}
{
number: "+1986543589455",
place: "Houston",
createdAt: ""
}
]
}
Here I only know the mongo id(_id) and phone number(+1786543589455) and I need to remove that whole corresponding array element from document. i.e zero indexed element in phone array is matched with phone number and need to remove the corresponding array element.
contact: {
phone: [
{
number: "+1986543589455",
place: "Houston",
createdAt: ""
}
]
}
I tried with following update method
collection.update(
{ _id: id, 'contact.phone': '+1786543589455' },
{ $unset: { 'contact.phone.$.number': '+1786543589455'} }
);
But it removes number: +1786543589455 from inner array object, not zero indexed element in phone array. Tried with pull also without a success.
How to remove the array element in mongodb?
Try the following query:
collection.update(
{ _id: id },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
It will find document with the given _id and remove the phone +1786543589455 from its contact.phone array.
You can use $unset to unset the value in the array (set it to null), but not to remove it completely.
You can simply use $pull to remove a sub-document.
The $pull operator removes from an existing array all instances of a value or values that match a specified condition.
Collection.update({
_id: parentDocumentId
}, {
$pull: {
subDocument: {
_id: SubDocumentId
}
}
});
This will find your parent document against given ID and then will remove the element from subDocument which matched the given criteria.
Read more about pull here.
In Mongoose:
from the document:
To remove a document from a subdocument array we may pass an object
with a matching _id.
contact.phone.pull({ _id: itemId }) // remove
contact.phone.pull(itemId); // this also works
See Leonid Beschastny's answer for the correct answer.
To remove all array elements irrespective of any given id, use this:
collection.update(
{ },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
To remove all matching array elements from a specific document:
collection.update(
{ _id: id },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
To remove all matching array elements from all documents:
collection.updateMany(
{ },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
Given the following document in the profiles collection:
{ _id: 1, votes: [ 3, 5, 6, 7, 7, 8 ] }
The following operation will remove all items from the votes array that are greater than or equal to ($gte) 6:
db.profiles.update( { _id: 1 }, { $pull: { votes: { $gte: 6 } } } )
After the update operation, the document only has values less than 6:
{ _id: 1, votes: [ 3, 5 ] }
If you multiple items the same value, you should use $pullAll instead of $pull.
In the question having a multiple contact numbers the same use this:
collection.update(
{ _id: id },
{ $pullAll: { 'contact.phone': { number: '+1786543589455' } } }
);
it will delete every item that matches that number. in contact phone
Try reading the manual.

How to increase counter on repetition of value under an array mongodb

I have a mongoose schema like this:
{
"_id" : ObjectId("5a7acda13b808dbed05d6505"),
"symbol" : "#AKS",
"counter" : 4
},
{
"_id" : ObjectId("5a7acda13b808dbed05d6506"),
"symbol" : "#AKD",
"counter" : 5
}
Now if I want to update multiple column i.e "symbol" values on one query then MongoDB updateMany Query will do. This is Query:
MyCollectionName.updateMany({ 'symbol' : { $in : req.body.array}}, { $inc : { 'counter': 1 } }, function(err, data) {
if(err) {
console.log('error')
} else {
console.log('value incremented')
}
});
By this Query If I give the Array value i.e
var array = ["#AKS", "#AKD"];
Then it will Increment the counter of Both. My Question is If I will Provide the Same value on an array then I want Two increment not one. Like this:
var array = ["#AKS", "#AKS"];
//I want the Increment of 2.
var array = [#AKS", "#AKS", "#AKS", "#AKS"]
//at this stage I want the counter of Increment is 4
But currently it will do Only One. Is this possible????
Any help is really appreciated.
You can use the bulkWrite API for this update as it allows you to compose an array of bulkWrite() write operations in which you can use the updateOne operation for each element in the array. The following example shows how you can apply it in your case:
let ops = [];
const handleResult = res => console.log(res);
const handleError = err => console.error(err);
req.body.array.forEach(function(item) {
ops.push({
"updateOne": {
"filter": { "symbol": item },
"update": { "$inc": { "counter": 1 } }
}
});
if (ops.length === 1000) {
MyCollectionName.bulkWrite(ops).then(handleResult).catch(handleError);
ops = [];
}
})
if (ops.length > 0) MyCollectionName.bulkWrite(ops).then(handleResult).catch(handleError);
what if instead of using updateMany you just forEach your array:
const yourArray = req.body.array;
yourArray.forEach(function(item) {
MyCollectionName.update({ 'symbol': { $in: item }}, { $inc: { 'counter': 1 }, function (err, data) {
// additional code
});
});

RethinkDB - is there a better way of doing this non-atomic update on a nested array?

I have two tables in a RethinkDB database - "leagues" and "players", with document structure as follows:
player
{
id: 1,
name: "Bob",
email: "bob#bob.com"
}
league
{
id: 1,
name: "L1",
standings: [{
"player_id": 1,
"position": 1
},{
"player_id": 2,
"position": 2
}]
}
What I'm trying to achieve is when a player is deleted, there is an obviously simple ReQL query to remove the player:
r.table("players").get(1).delete().run(conn, callback);
But I also need to remove that player from any leagues that they are playing in, and then update the positions of all other players in that league so they become sequential again.
This is the query I have to remove a player with id "2":
r.table("leagues").getAll(2, { index: "player_id" }).update({
standings: r.row("standings").filter(function(standing) {
return standing("player_id").ne(2)
}).map(function(standing) {
return r.branch (
standing("position").gt(
r.table("leagues").getAll(2, { index: "player_id" }).nth(0)("standings").filter(function(s) {
return s("player_id").eq(2)
}).nth(0)("position")
),
standing.merge({ position: standing("position").sub(1) }),
standing
)
})
}, {
nonAtomic: true
})
And this is working, it removes the deleted player, and then shuffles the positions of the remaining players to fill the gap and become sequential again.
My question is, is there a better way? One where I don't have to specify the query as nonAtomic? I can only think that I'd need to do a number of separate queries first, to find the position of the player I want to remove, so that can be passed in as a variable rather than a subquery as I believe it's that part that is making this nonAtomic.
Cheers
I assume the standings always match the position in the array?
If so, you could write it like this:
r.table('leagues').getAll(2, {index: 'player_id'}).update(function(row) {
return {
standings: row('standings').filter(function (standing) {
return standing('player_id').ne(2);
}.map(r.range(), function(standing, index) {
return standing.merge({position: index.add(1)});
})
};
});
If not, you could write it like this:
r.table('leagues').getAll(2, {index: 'player_id'}).update(function(row) {
return row('standings').filter(function (standing) {
return standing('player_id').eq(2);
}).nth(0)('position').do(function(pos) {
return {
standings: row('standings').filter(function (standing) {
return standing('player_id').ne(2);
}).map(function(standing) {
return r.branch(
standing('position').gt(pos),
standing.merge({position: standing('position').sub(1)}),
standing);
})
};
});
});
{
standings: row('standings').filter(function (standing) {
return standing('player_id').ne(2)
}.map(r.range(), function(standing, index) {
return standing.merge({position: index.add(1)});
})
};
})

Get observableArray of unique values from another obseravableArray

I'm having data like:
{ mealName: "sandwich", price: 0 },
{ mealName: "lobster", price: 34.95 },
{ mealName: "whole zebra", price: 290 },
{ mealName: "whole zebra", price: 290 },
{ mealName: "sandwich", price: 290 },
{ mealName: "whole zebra", price: 290 }
And I want to get observableArray like:
{ mealName: "sandwich"},
{ mealName: "whole zebra"},
{ mealName: "lobster"}
I've tried to reproduce it in jsFiddle, but something is wrong
unique doesn't have to be an observable array as the availableMeals is not an observable array, but since you asked I used a computed.
First I sort the array using a custom sort function. I used concat to create a copy as I didn't want to modify the order of the original array. I then loop through the sorted array and remove any duplicates:
self.unique = ko.computed(function() {
var sortedItems = self.availableMeals.concat().sort(function(left, right) { return left.mealName == right.mealName ? 0 : (left.mealName < right.mealName ? -1 : 1) });
var meal;
for (var i = 0; i < sortedItems.length; i++) {
if (!meal || meal != sortedItems[i].mealName) {
meal = sortedItems[i].mealName;
}
else {
sortedItems.splice(i, 1);
i--;
}
}
return sortedItems;
});
jsfiddle
var uniqueMeals = ko.computed(function () {
var uniqueMealNames = mealData().reduce(function(uniqueMeals, meal) {
if(uniqueMeals.indexOf(meal.mealName) === -1) {
uniqueMeals.push(meal.mealName);
}
return uniqueMeals;
}, []);
return uniqueMealNames.map(function(mealName) {
return {mealName: mealName};
});
});
Some suggestions: it's probably be better to store the unique values in an array of strings, not objects, i.e. ["sandwich", "whole zebra", "lobster"]. If so, you can drop the last line from my above code.
Also, you might consider looking into the lodash library. With lodash, this would be:
var uniqueMeals = ko.computed(function () {
return _.unique(_.pluck(mealData, "mealName"));
})

Resources