Using functions for creating an index in MongoDB? - mongodb

I have a blog that allow users to vote for articles. The documents of an article looks like:
{
title: "title of post",
body: "text text text text text text",
votes: [
{user: "user1", points: 2},
{user: "user13", points: 1},
{user: "user30", points: 1},
{user: "user2", points: -1},
{user: "user51", points: 3},
],
sum_of_votes: 6
}
I'd like to sort by the number of votes the post received. At the moment I added a field sum_of_votes that needs to be updated every time somebody voted. Since votes already contains the raw-data for sum_of_votes I thought there might be a more elegant way. I came up with two ideas:
Using a function while creating an index, for example:
db.coll.ensureIndex({{$sum: votes.points}: 1})
Having a dynamic field. The document could look like this:
{
title: "title of post",
body: "text text text text text text",
votes: [
{user: "user1", points: 2},
{user: "user13", points: 1},
{user: "user30", points: 1},
{user: "user2", points: -1},
{user: "user51", points: 3},
],
sum_of_votes: {$sum: votes.points}
}
In those cases I only had to update the votes-array. Is something like this possible in MongoDB?

Depending on how you do the update on the votes array, either as updating the document by inserting a new vote in the votes array or updating an existing vote, an approach you could take is use the $inc operator on the sum_of_votes with the value you are using to update the votes.points with. For example, in the case where you are inserting a new vote for a user with value in variable userId and the actual vote points in variable points (which must be a number), you could try:
db.articles.update(
{"_id" : article_id},
{
"$addToSet": {
"votes": {
"user": userId,
"points" : points
}
},
"$inc": { "sum_of_votes": points }
}
)
And you will need to factor in Neil Lunn's point where you wouldn't want the same user to vote more than once in your updates.

Related

Pull Document By ID from Triple Nested Array MongoDB

I'd like to be able to pull a document by id from a triple nested array of documents. DB looks something like this:
[{
type: "Foods",
fruit: [{
name: "Apple",
kinds: [{
name: "Red Delicious"
details: [{
_id: ObjectId("123123123"),
colors: ["red", "yellow"]
}]
}]
}]
}]
I'd like to be able to pull the document with the _id: 123123123. I've tried many different ways, but it always says it matches, but won't modify the document.
I've tried:
db.stuff.update({}, {$pull: {fruits: {kinds: {details: {_id: "123123123"}}}}}),
db.stuff.update({}, {$pull: {"fruits.kinds.details' : {_id: "123123123"}}}),
db.stuff.update({}, {$pull: {"fruits.$[].kinds.$[].details' : {_id: "123123123"}}})
But everytime it matches, but won't delete the document.
Please help.
The last attempt is correct however you need to fix two things: fruit instead of fruit (according to your sample data) and types needs to match so you have to convert string to ObjectId
db.stuff.update({}, {$pull: {"fruit.$[].kinds.$[].details' : {_id: mongoose.Types.ObjectId("123123123")}}})

Multilayered find and update mongodb and nodejs

Learning MongoDB so I hope that this is not a duplicate question.
So big picture. Users can add/remove items from their cart (which is an array).
The cart array holds objects who are structured as follows: {_id: 123r32, qty: 2}
The user document is structured as follows:
_id: 123432423,
username: johndoe,
email: johndoe#mail.com,
cart: [
{
_id: wefw3423,
qty: 100
}
]
So adding to the array is fine:
db.users.updateOne({_id: req.user.id}, {$push: {cart: {_id: req.body.id, qty: req.body.qty}}})
But when I push, I don't want to add a duplicate. I want to just adjust the qty (add/subtract) accordingly. If no instance of that _id exists, push.
Thank you for reading and your help! Should you need additional information, please ask I shall provide!.
EDIT:
Additional information. The request body (req.body) will contain only ONE item. So updateMany will not be required here.
db.users.updateOne({_id: req.user.id}, {$set: { "cart.$[element]": {_id:"wefw3423",qty:50}}, { arrayFilters: [ { element: _id:"wefw3423" } ]},{ upsert:true})
MongoDB reference :
https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/#mongodb-update-up.---identifier--

$unwind, produce complement of results in addition to target of "path"

I have a mongo collection called scorerecords that has documents that look like:
{status: 'in progress', teams: {count: 2, team: [ {name: "team 1", score: 100}, {name: "team 2", score: 95}]}}
I would like to return a unique record for each element in the "teams.team" array (as can be done with $unwind), but have it also return the opposite team record (the "opponent"). Example:
{status: 'in progress', teams: {count: 2, team: {name: "team 1", score: 100}, opponent: {name: "team 2", score: 95}}}
{status: 'in progress', teams: {count: 2, team: {name: "team 2", score: 95}, opponent: {name: "team 1", score: 100}}}
This way, I would have 1 record per team (instead of 1 per matchup), and could calculate the number of times a team won or lost (e.g. by using a $project statement to define a "winner:true/false" field or something similar).
Is there any way to do that with some combination of $aggregate functions like $project/$unwind?
I ended up finding a solution:
db.scorerecords.aggregate([{$addFields: {"teams.team": {$map: {input: "$teams.team", as: "tm", in: {"name": "$$tm.name", "score":"$$tm.score", "idx": {$indexOfArray: ["$teams.team", "$$tm"]} }}}}},{$addFields: {"teams.team": {$map: {input: "$teams.team", as: "tm", in: {"name": "$$tm.name", "score": "$$tm.score", "opp": {$arrayElemAt: ["$teams.team", {$subtract: [1, "$$tm.idx"]}]}} }}}}, {$unwind: "$teams.team"} ])
It seems like there might have been a more convenient/efficient method, but this way seems to return the desired result.
Steps:
1) Add a field that identifies the "index" of the current item in the array with a $map call.
2) Using another $map call, add an "opp" field, assuming the opp is the (1-idx)th entry in the array (see caution below).
3) Use $unwind to flatten, creating 2x the original recordset with the complementary records included.
Caution on step #2:
(This assumes that every scorerecord will have <=2 teams. If there is only 1, the "opp" field will not be populated, which is desirable. It will have unexpected results if there are more than 2 teams in the "teams.team" array. (e.g. for every team beyond the 2nd, it will return the last element in the array as the "opp").

Mongo query: array of objects where a key's value is repeated

I am new to Mongo. Posting this question because i am not sure how to search this on google
i have a book documents like below
{
bookId: 1
title: 'some title',
publicationDate: DD-MM-YYYY,
editions: [{
editionId: 1
},{
editionId: 2
}]
}
and another one like this
{
bookId: 2
title: 'some title 2',
publicationDate: DD-MM-YYYY,
editions: [{
editionId: 1
},{
editionId: 1
}]
}
I want to write a query db.books.find({}) which would return only those books where editions.editionId has been duplicated for a book.
So in this example, for bookId: 2 there are two editions with the editionId:1.
Any suggestions?
You can use the aggregation framework; specifically, you can use the $group operator to group the records together by book and edition id, and count how many times they occur : if the count is greater than 1, then you've found a duplication.
Here is an example:
db.books.aggregate([
{$unwind: "$editions"},
{$group: {"_id": {"_id": "$_id", "editionId": "$editions.editionId"}, "count": {$sum: 1}}},
{$match: {"count" : {"$gt": 1}}}
])
Note that this does not return the entire book records, but it does return their identifiers; you can then use these in a subsequent query to fetch the entire records, or do some de-duplication for example.

MongoDB: Upsert document in array field

Suppose, I have the following database:
{
_id: 1,
name: 'Alice',
courses: [
{
_id: 'DB103',
credits: 6
},
{
_id: 'ML203',
credits: 4
}
]
},
{
_id: 2,
name: 'Bob',
courses: []
}
I now want to 'upsert' the document with the course id 'DB103' in both documents. Although the _id field should remain the same, the credits field value should change (i.e. to 4). In the first document, the respective field should be changed, in the second one, {_id: 'DB103', credits: 4} should be inserted into the courses array.
Is there any possibility in MongoDB to handle both cases?
Sure, I could search with $elemMatch in courses for 'DB103' and if I haven't found it, insert, otherwise update the value. But these are two steps and I would like to do both in just one.