MongoDB Aggregation: Counting distinct fields from array - javascript

Need to count distinct tags on all registers in a mongodb query, using JS.
Register structure:
{
"_id": {
"$oid": "62e593aed8fd9808777225e8"
},
"title": "“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”",
"author": {
"name": "Albert Einstein",
"url": "https://quotes.toscrape.com/author/Albert-Einstein"
},
"tag": [
"change",
"deep-thoughts",
"thinking",
"world"
]
}

This could be useful. In addition to get the different values for the field, it returns the number of appearances:
db.collection.aggregate([
{
"$unwind": "$tag"
},
{
"$group": {
"_id": "$tag",
"total": {
"$sum": 1
}
}
},
])
You can try here: https://mongoplayground.net/p/yXLYkJKO3Wf

Related

Update a field in nested array of objects containing another array of objects in mogoDb (JavaScript)

I have the following structure of Student collection in MongoDb:
{
"_id": "st1",
"student_courses": [
{
"_id": "c1",
"course_name": "Node",
"image": [
{
"_id": "c1img1",
"image": "img1.jpeg"
}
]
},{
"_id": "c2",
"course_name": "React",
"image": [
{
"_id": "c2img2",
"image": "img2.jpeg"
}
]
}
],
}
Now, I want to update the image name of img1.jpeg in all the documents that have same image name. So what I am doing is this:
Student.updateMany(
{ "student_courses._id": "c1"},
{
$set: {
"student_courses.$.course_name": "Node Crash Course",
"student_courses.$.image[0].image": complete_image_name,
},
}
);
Unexpectedly, this is updating course_name field but image. I have tried using $ positional argument instead of [0] but got the error Too many positional arguments ...... I don't know how to do that. My expected output should look like this:
{
"_id": "st1",
"student_courses": [
{
"_id": "c1",
"course_name": "Node Crash Course",
"image": [
{
"_id": "c1img1",
"image": "complete_image_name.jpeg"
}
]
},
:::::::::::::::::::
:::::::::::::::::::
],
}
{
"_id": "st2",
"student_courses": [
{
"_id": "c1",
"course_name": "Node Crash Course",
"image": [
{
"_id": "c1img1",
"image": "complete_image_name.jpeg"
}
]
},
:::::::::::::::::::
:::::::::::::::::::
],
}
Moreover, I have implemented almost every method posted in similar questions. Thanks in advance for any help.
Try to change the way you are referring to the first element in the array:
Student.updateMany(
{ "student_courses._id": "c1"},
{
$set: {
"student_courses.$.course_name": "Node Crash Course",
"student_courses.$.image.0.image": complete_image_name,
},
}
);

how to fetch items if any search keyword matched?

I have this document in my elastic DB.I am using this package
https://www.npmjs.com/package/#elastic/elasticsearch
I am searching on title.
I am doing like this.
const result= await client.search({
index: 'products',
"query": {
"bool": {
"should": [
{
"wildcard": { "title": "*" + search + "*" }
}
],
"minimum_should_match": 1
}
}
when I search "title" this issue . it give me one result but When I am seacrhing i have issue . it is giving zero or 0 result.why ? is it possible to fetch that data.issue keyword is present in first collection why it is not picking ?
[
{
"_index": "products",
"_id": "wZRh3n8Bs9qQzO6fvTTS",
"_score": 1.0,
"_source": {
"title": "laptop issues",
"description": "laptop have issue present in according"
}
},
{
"_index": "products",
"_id": "wpRh3n8Bs9qQzO6fvzQM",
"_score": 1.0,
"_source": {
"title": "buy mobile",
"description": "mobile is in Rs 250"
}
},
{
"_index": "products",
"_id": "w5Rh3n8Bs9qQzO6fvzTz",
"_score": 1.0,
"_source": {
"title": "laptop payment",
"description": "laptop payment is given in any way"
}
}
]
how to search on keywords? any keyword match . I need to pick that whole colloection
If you want to match only some of the word from query then you can use match query with operator set to or.
{
"query": {
"match": {
"title": {
"query": "i have issues",
"operator": "or"
}
}
}
}
Update
To run the query which you mentioned in comment, You can use match_bool_prefix query.
{
"query": {
"match_bool_prefix": {
"title": {
"query": "i have issu"
}
}
}
}

How can I merge two mongo collections?

I am banging my head against the wall on this...
SEE UPDATE 1 (below) !
I am merging two collections together... I looked at this example ( and ~several~ other examples here on SO ... )
https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#lookup-single-equality
I think I am really close, but my expected results are not the same as what I would expect out of the example.
Here is the schema for 'Event'
const EventSchema = new Schema({
name: {type: String, required: true},
})
Here is some 'Event' data
[
{
"_id": "5e8e4fcf781d96df5c1f5358",
"name": "358 Event"
},
{
"_id": "5e8e55c5a0f5fc1431453b5f",
"name": "b5f Event"
}
]
Here is 'MyEvent' schema:
const MyEventSchema = new Schema({
userId: {type: Schema.Types.ObjectId, required: true},
eventId: {type: Schema.Types.ObjectId, required: true},
})
Here is some 'MyEvent' data
[
{
"_id": "5e8f4ed2ddab5e3d04ff30b3",
"userId": "5e6c2dddad72870c84f8476b",
"eventId": "5e8e4fcf781d96df5c1f5358",
}
]
Here is my code ( the code is wrapped in a promise so it returns resolve and reject with data )
var agg = [
{
$lookup:
{
from: "MyEvent",
localField: "_id",
foreignField: "eventId",
as: "userIds"
}
}
];
Event.aggregate(agg)
.then( events => {
return resolve(events);
})
.catch(err => {
return reject(null);
})
Here are my results,
[
{
"_id": "5e8e4fcf781d96df5c1f5358",
"name": "358 Event",
"__v": 0,
"UserIds": []
},
{
"_id": "5e8e55c5a0f5fc1431453b5f",
"name": "b5f Event",
"__v": 0,
"UserIds": []
}
]
I expect to see UserIds filled in for event '358 Event', like this
What am I missing ???
[
{
"_id": "5e8e4fcf781d96df5c1f5358",
"name": "358 Event",
"__v": 0,
"UserIds": [
{"userId": "5e6c2dddad72870c84f8476b"}
]
},
{
"_id": "5e8e55c5a0f5fc1431453b5f",
"name": "b5f Event",
"__v": 0,
"UserIds": []
}
]
UPDATE 1
I found a mongo playground and what I have works there, but it doesn't work in my code ??
https://mongoplayground.net/p/fy-GP_yx5j7
In case the link breaks, here is configuration: * select 'bson multiple collections'
db={
"collection": [
{
"_id": "5e8e4fcf781d96df5c1f5358",
"name": "358 Event"
},
{
"_id": "5e8e55c5a0f5fc1431453b5f",
"name": "b5f Event"
}
],
"other": [
{
"_id": "5e8f4ed2ddab5e3d04ff30b3",
"userId": "5e6c2dddad72870c84f8476b",
"eventId": "5e8e4fcf781d96df5c1f5358",
}
]
}
Here is Query:
db.collection.aggregate([
{
$lookup: {
from: "other",
localField: "_id",
foreignField: "eventId",
as: "userIds"
}
}
])
Here is the result:
[
{
"_id": "5e8e4fcf781d96df5c1f5358",
"name": "358 Event",
"userIds": [
{
"_id": "5e8f4ed2ddab5e3d04ff30b3",
"eventId": "5e8e4fcf781d96df5c1f5358",
"userId": "5e6c2dddad72870c84f8476b"
}
]
},
{
"_id": "5e8e55c5a0f5fc1431453b5f",
"name": "b5f Event",
"userIds": []
}
]
any suggestions as to why this doesn't work in my code... but works in the playground?
UPDATE 2
I found this:
Need a workaround for lookup of a string to objectID foreignField
UPDATE 3
I have changed the schema to use ObjectId for ids now
still doesn't work
And they are ObjectIds :
RESOLUTION:
So the real answer was a combination of Update 2 and Update 3 and using the right collection name in the lookup.
Update 2 is pretty much my very same question... just using different table names
Update 3 is the correct way to solve this issue.
Mohammed Yousry pointed out the collection name might be wrong... so I looked at my schema and I did have it wrong - changed the name to the right name (along with ObjectId types) and it worked !
It seems there's a typo in from property in $lookup, MyEvent maybe not the collection name
db.collection.aggregate([
{
$lookup: {
from: "MyEvent", // here is the issue I think, check the collection name and make sure that it matches the one you write here
localField: "_id",
foreignField: "eventId",
as: "userIds"
}
}
])
in mongo playground you attached in the question, if you change the 'other' in the $lookup to anything else, or make a typo in it .. like others instead of other, you will face the same issue
so check that there is no typo in the word MyEvent that you populate from

MongoDB aggregate function is not returning the value of collection joined using JavaScript

I needed assistance in order to work out why the aggregate function is not responding the way I'd expect it to respond. This is a RESTful API service I've designed in which I am trying to connect collections with each other. Please note the following:
Collection: Season
{
"_id": {
"$oid": "5c0fc60bfb6fc04dd6ea4e9a"
},
"Season": "1",
"TotalEpisode": "15",
"Name": null,
"Description": "First season with no name for this drama",
"PlayID": "5c0fc4aafb6fc04dd6ea4d81"
}
Collection: Play
{
"_id": {
"$oid": "5c0fc4aafb6fc04dd6ea4d81"
},
"Name": "It was the first time",
"Description": "One of the best action heros in the entertainment industry until this day",
"ReleaseDate": "24/12/2010",
"EndingDate": "12/08/2012",
"Category": "Drama"
}
My implemented code in JavaScript
function getTestLookUp(db, collectionName, response, secondCollectionName){
console.log('First collection name: ' + collectionName + '\n' + 'Second collection name: ' + secondCollectionName);
db.collection(collectionName).aggregate([
{
$lookup:
{
from: secondCollectionName,
localField: 'PlayID',
foreignField: '_id',
as: 'requestedDetails'
}
}
]).toArray((err, res) => {
if(err){
console.log(err);
} else {
console.log(res);
response.status(200).json({
'Items': res
});
}
});
}
The response
{
"Items": [
{
"_id": "5c0fc60bfb6fc04dd6ea4e9a",
"Season": "1",
"TotalEpisode": "15",
"Name": null,
"Description": "First season with no name for this drama",
"PlayID": "5c0fc4aafb6fc04dd6ea4d81",
"requestedDetails": []
}
]
}
The things I've checked so far: the collection names are accurate, the ID is also accurate as I can search it up on the MLabs search feature. I don't understand as to why this is returning a empty 'requestedDetails' as I hoped it would return the item from the Play collection.
In addition to this, I would also appreciate if someone can point out how I can join multiple collections instead of 2.
I welcome any questions regarding this problem.
While still researching for this issue, I accidentally came across a another problem in which someone wrote a comment stating that "you might be comparing a String with ObjectID". This was the cause for this error as I obtain a String variable in return from the database and I am comparing the String variable with the _id which is expecting to see a ObjectID variable to complete the query. Therefore, meaning that my query/lookup is never matching these two variables.
The only way tackle this issue is to do a conversion (string to ObjectID) and then compare the values. However, since I'm using the version of ^3.1.10 of MongoDB, this functionality is not possible. Will need to update the version to 4.0 to be able to implement this functionality.
In order to rectify this issue, I managed to surround the foreign ID within $iod tags.
Before
{
"_id": {
"$oid": "5c0fc60bfb6fc04dd6ea4e9a"
},
"Season": "1",
"TotalEpisode": "15",
"Name": null,
"Description": "First season with no name for this drama",
"PlayID": "5c0fc4aafb6fc04dd6ea4d81"
}
After
{
"_id": {
"$oid": "5c0fc60bfb6fc04dd6ea4e9a"
},
"Season": "1",
"TotalEpisode": "15",
"Name": null,
"Description": "First season with no name for this drama",
"PlayID": {
"$oid": "5c0fc4aafb6fc04dd6ea4d81"
}
}
Response
{
"Items": [
{
"_id": "5c0fc60bfb6fc04dd6ea4e9a",
"Season": "1",
"TotalEpisode": "15",
"Name": null,
"Description": "First season with no name for this drama",
"PlayID": "5c0fc4aafb6fc04dd6ea4d81",
"Details": [
{
"_id": "5c0fc4aafb6fc04dd6ea4d81",
"Name": "It was the first time",
"Description": "One of the best action heros in the entertainment industry until this day",
"ReleaseDate": "24/12/2010",
"EndingDate": "12/08/2012",
"Category": "Drama"
}
]
}
]
}

Sorting records on number of embedded documents

I am using mongodb and mongoose, And I have a situation where a user creates a rating and a review, what I want is to fetch review of the product whose review has the highest number of votes. Is it possible to do it in mongo?
The structure is
{ "product" : ObjectId("53ccfa53b502542b2f463acd"),
"_id" : ObjectId("53e8675aea39355818ec4ab2"),
"vote" : [ ],
"review" : "Blah Blah Blah",
"stars" : 3,
"email" : "user#example.com", "__v" : 0 }
Now I want to show the review that has achieved the most votes, I know if after find() I put sort() and limit() function, it can be achieved through a field present on the same document level however I do not know how to handle multiple records in this case 'vote'....
The best thing you can do is to maintain a "voteCount" on the document itself. The reasons will become self apparent in a moment.
You can maintain this as members are added or removed from the array. Let's say you are using an ObjectId and the $push and $pull update operators to do this. So you also you $inc with a bit of query logic to make sure you don't duplicate the "User ObjectId" casting the vote. Assuming a model called "Product":
Product.update(
{
"_id": ObjectId("53e8675aea39355818ec4ab2"),
"votes": { "$ne": ObjectId("53e87caaca37ffa384e5a931") }, // the user ObjectId
},
{
"$push": { "votes": ObjectId("53e87caaca37ffa384e5a931" }, // same Id
"$inc": { "voteCount": 1 }
},
function(err) {
}
);
And to remove:
Product.update(
{
"_id": ObjectId("53e8675aea39355818ec4ab2"),
"votes": ObjectId("53e87caaca37ffa384e5a931"), // the user ObjectId
},
{
"$pull": { "votes": ObjectId("53e87caaca37ffa384e5a931" }, // same Id
"$inc": { "voteCount": -1 }
},
function(err) {
}
);
Then it's just a matter of sorting on the field:
Product.find().sort({ "voteCount": -1 }).limit(1).exec(function(err,doc) {
});
But if for some reason you cannot see fit to keep the "voteCount" in the document, then you need to manually "project" this with the aggregation framework. Etiher using the $size aggregate method where you have MongoDB 2.6 or greater:
Product.aggregate(
[
{ "$project": {
"product": 1,
"vote": 1,
"review": 1,
"stars": 1,
"email": 1,
"voteCount": { "$size": "$vote" }
}},
{ "$sort": { "voteCount": -1 } },
{ "$limit": 1 }
],
function(err,result) {
}
);
Or by $unwind on the array and getting the count via $sum for earlier versions:
Product.aggregate(
[
{ "$unwind": "$vote"
{ "$group": {
"_id": "$_id",
"product": { "$first": "$product" },
"vote": { "$push": "$vote" },
"review": { "$first": "$review" },
"stars": { "$first": "$stars" },
"email": { "$first": "$email" },
"voteCount": { "$sum": 1 }
}},
{ "$sort": { "voteCount": -1 } },
{ "$limit": 1 }
],
function(err,result) {
}
);
The aggregation approach really does not make that much sense to implement unless you really need other calculations than the array length. So best just to keep it in the document.
The best way to do so with MongoDB is to add new counter field to explicitly store the number of votes:
{ "product" : ObjectId("53ccfa53b502542b2f463acd"),
"_id" : ObjectId("53e8675aea39355818ec4ab2"),
"vote" : [ {...}, {...}, {...} ],
"vote_count": 3, // <-- new field
"review" : "Blah Blah Blah",
"stars" : 3,
"email" : "user#example.com", "__v" : 0 }
Of course, you have other options, like using Aggregation Pipeline. But adding new field is the best option, because it'll allow you to build an index on this field and do indexed queries.

Categories