How to implement localisation with HapiJs and MongoDB - node.js

MongoDB return the following data:
{
name: {
"en": "any name",
"ar": "اي اسم"
}
}
What can be done, if language chosen is ar and want this output:
{
name: "اي اسم"
}
Thanks!

You can use the aggregation framework built in to MongoDB, e.g.
db.LocalNames.aggregate([
{$match: {"name.ar": {$exists: true}}},
{$project: {"name": "$name.ar"}}
])
This will match only documents with an Arabic name and then project just that value as name

Related

how can i sort data with a array element in mongodb without using unwind

this is my sample data in this I have a userId and a array "watchHistory", "watchHistory" array contains the list of videos that is watched by the user :
{
"_id": "62821344445c30b35b441f11",
"userId": 579,
"__v": 0,
"watchHistory": [
{
"seenTime": "2022-05-23T08:29:19.781Z",
"videoId": 789456,
"uploadTime": "2022-03-29T12:33:35.312Z",
"description": "Biography of Indira Gandhi",
"speaker": "andrews",
"title": "Indira Gandhi",
"_id": "628b45df775e3973f3a670ec"
},
{
"seenTime": "2022-05-23T08:29:39.867Z",
"videoId": 789455,
"uploadTime": "2022-03-31T07:37:39.712Z",
"description": "What are some healthy food habits to stay healthy",
"speaker": "morris",
"title": "Healthy Food Habits",
"_id": "628b45f3775e3973f3a670"
},
]
}
I need to match the userId and after that i need to sort it with "watchHistory.seenTime", seenTime field indicates when the user saw the video. so i need to sort like the last watched video should come first in the list.
I don't have permission to use unwind so can any one help me from this. Thank you.
If you are using MongoDB version 5.2 and above, you can use $sortArray operator in an aggregation pipeline. Your pipeline should look something like this:
db.collection.aggregate(
[
{"$match":
{ _id: '62821344445c30b35b441f11' }
},
{
"$project": {
_id: 1,
"userId": 1,
"__v": 1,
"watchHistory": {
"$sortArray": { input: "$watchHistory", sortBy: { seenTime: -1 }}
}
}
}
]
);
Please modify the filter for "$match" stage, according to the key and value you need to filter on. Here's the link to the documentation.
Without using unwind, it's not possible to do it via an aggregation pipeline, but you can use update method and $push operator, as a workaround like this:
db.collection.update({
_id: "62821344445c30b35b441f11"
},
{
$push: {
watchHistory: {
"$each": [],
"$sort": {
seenTime: -1
},
}
}
})
Please see the working example here

MongoDB aggregation $group stage by already created values / variable from outside

Imaging I have an array of objects, available before the aggregate query:
const groupBy = [
{
realm: 1,
latest_timestamp: 1318874398, //Date.now() values, usually different to each other
item_id: 1234, //always the same
},
{
realm: 2,
latest_timestamp: 1312467986, //actually it's $max timestamp field from the collection
item_id: 1234,
},
{
realm: ..., //there are many of them
latest_timestamp: ...,
item_id: 1234,
},
{
realm: 10,
latest_timestamp: 1318874398, //but sometimes then can be the same
item_id: 1234,
},
]
And collection (example set available on MongoPlayground) with the following schema:
{
realm: Number,
timestamp: Number,
item_id: Number,
field: Number, //any other useless fields in this case
}
My problem is, how to $group the values from the collection via the aggregation framework by using the already available set of data (from groupBy) ?
What have been tried already.
Okay, let skip crap ideas, like:
for (const element of groupBy) {
//array of `find` queries
}
My current working aggregation query is something like that:
//first stage
{
$match: {
"item": 1234
"realm" [1,2,3,4...,10]
}
},
{
$group: {
_id: {
realm: '$realm',
},
latest_timestamp: {
$max: '$timestamp',
},
data: {
$push: '$$ROOT',
},
},
},
{
$unwind: '$data',
},
{
$addFields: {
'data.latest_timestamp': {
$cond: {
if: {
$eq: ['$data.timestamp', '$latest_timestamp'],
},
then: '$latest_timestamp',
else: '$$REMOVE',
},
},
},
},
{
$replaceRoot: {
newRoot: '$data',
},
},
//At last, after this stages I can do useful job
but I found it a bit obsolete, and I already heard that using [.mapReduce][1] could solve my problem a bit faster, than this query. (But official docs doesn't sound promising about it) Does it true?
As for now, I am using 4 or 5 stages, before start working with useful (for me) documents.
Recent update:
I have checked the $facet stage and I found it curious for this certain case. Probably it will help me out.
For what it's worth:
After receiving documents after the necessary stages I am building a representative cluster chart, that you may also know as a heatmap
After that I was iterating each document (or array of objects) one-by-one to find their correct x and y coordinated in place which should be:
[
{
x: x (number, actual $price),
y: y (number, actual $realm),
value: price * quantity,
quantity: sum_of_quantity_on_price_level
}
]
As for now, it's old awful code with for...loop inside each other, but in the future, I will be using $facet => $bucket operators for that kind of job.
So, I have found an answer to my question in another, but relevant way.
I was thinking about using $facet operator and to be honest, it's still an option, but using it, as below is a bad practice.
//building $facet query before aggregation
const ObjectQuery = {}
for (const realm of realms) {
Object.assign(ObjectQuery, { `${realm.name}` : [ ... ] }
}
//mongoose query here
aggregation([{
$facet: ObjectQuery
},
...
])
So, I have chosen a $project stage and $switch operator to filter results, such as $groups do.
Also, using MapReduce could also solve this problem, but for some reason, the official Mongo docs recommends to avoid using it, and choose aggregation: $group and $merge operators instead.

MongoDb, NodeJs - How to find a document by searching it by id of an array element

This is my Model :-
"_id":{
"$oid":"5f0dbca73ef98355649d7cc7"
},
"name":"Multiple Image test",
"description":"Awesome",
"price":{
"$numberInt":"15000"
},
"images":[
{
"_id":{
"$oid":"5f0dbca73ef98355649d7cc9"
},
"data":{
"$binary":{
"base64":"random buffer data",
"subType":"00"
}
},
"contentType":"image/jpeg"
},
{
"_id":{
"$oid":"5f0dbca73ef98355649d7cc8"
},
"data":{
"$binary":{
"base64":"Random buffer data",
"subType":"00"
}
},
"contentType":"image/jpeg"
}
],
}
Now how can i access a particular image data from the images field ?
I am using mongoose js so which query can be used and how to use it to access the data. Any kind of help would be appreciated.
And to boil Asiri's comment into something that fits your case:
You use the $elemMatch operator: https://docs.mongodb.com/manual/reference/operator/query/elemMatch/
MyModel.find({'images': {$elemMatch: {_id: ObjectId("5f0dbca73ef98355649d7cc8")}}}
You might not need the cast to ObjectId. (Now you should be able to guess that I haven't tested this answer, and for that I'm profoundly sorry.)

Mongodb 3.6 using multiple conditions on aggregation lookup

I have two collections in my MongoDb
metricCollectionForms
metric
A metricCollectionForms document looks something like this
{
"_id": ObjectId("5ea25f38afd94f0008d4e6f2"),
"approverId": "f08ba2aa-4597-41f0-9e6c-cebf1715ba30",
"formName": "Test Form",
"formId": "d56209a1-4df0-48de-b6cf-d1ee50200936",
}
I have skipped few attributes from the document above because they are not relevant to this question.
As for the metric a typical document looks like this
{
"_id": ObjectId("5ea27955bae4900008d996ba"),
"name": "Test Metric",
"type": "INTERNAL",
"formula": [
{
"type": "FORM_FIELD",
"formId": "d56209a1-4df0-48de-b6cf-d1ee50200936",
"formFieldId": "dca2bacf-2cbd-480d-8289-6f3050b635fb"
},
{...}, {...}
],
"formulaLabel": "monthly_production",
"createdBy": "f08ba2aa-4597-41f0-9e6c-cebf1715ba30",
"isApproved": true,
"isActive": false
}
You will notice that the formId value from metricCollectionForms is being referenced in the formula array formId field of the metric document.
So one metric can use many forms within its formula array of object.
I am trying to get list of forms and within that list return the array of metric name where
metric.formula.formId = formId (of the form) AND
metric.isActive = true AND
metric.isApproved = true
So far my aggregate query looks something like this:
{
$lookup: {
from: "metric",
localField: "formId",
foreignField: "formula.formId",
as: "metrics"
}
},
{
$addFields: {
metrics: "$metrics.name"
}
}
Now it does return me an array of all metric names but I do not know how I can apply the isApproved and isActive true conditions on the $lookup.
I tried doing $pipeline / $match etc but nothing seems to work. Also the solution should be compatible to Mongo 3.6 as I am using this within AWS DocumentDb (which only supports 3.6).
The pipeline form of lookup that would allow this is not available until MongoDB 4.2.
The best option in 3.6 is to follow the lookup with an $addFields stage with a $filter expression to remove the unwanted elements from the array.
The following should work for your example.
{
$lookup: {
from: "metric",
localField: "formId",
foreignField: "formula.formId",
as: "metrics"
}
},
{
$match: {
$and: {[
"metrics.isApproved": true,
"metrics.isActive": true
]}
}
}
{ $match: {"metrics.isApproved": 'MA'}},

Most efficient way to check if element exists in a set

so in my MongoDB database I have a collection holding user posts.
Within that collection I have a set called "likes", which holds an array of the ids of the users that have liked that post. When querying I would like to pass a user id to my query and have a boolean in the result telling me whether the id exists in the array to see whether the user has already liked the post. I understand this would be easy to do with two queries, one to get the post and one to check if the user has liked it, but I would like to find the most efficient way to do this.
For example, one of my documents looks like this
{
_id: 24jef247jos991,
post: "Test Post",
likes: ["userid1", "userid2"]
}
When I query from "userid1" I would like the return
{
_id: 24jef247jos991,
post: "Test Post",
likes: ["userid1", "userid2"],
userLiked: true
}
But when I query from let's say "userid3" I would like
{
_id: 24jef247jos991,
post: "Test Post",
likes: ["userid1", "userid2"],
userLiked: false
}
You can add the $addFields stage checking each of the document likes arrays against the input user.
db.collection.aggregate( [
{
$addFields: {
"userLiked":{ $in: [ "userid1", "$likes" ] }
}
}
] )
Starting from MongoDB 3.4 you can use the $in aggregation operator to check if an array contains a given element. You can use the $addFields operator aggregation operator to add the newly computed value to your document without explicitly including other fields.
db.collection.aggregate( [
{ "$addFields": { "userLiked": { "$in": [ "userid1", "$likes" ] } } }
])
In MongoDB 3.2, you can use the $setIsSubset operator and the square bracket [] operator to do this. The downside of this approach is that you need to manually $project all the field in your document. Also the $setIsSubset operator with de-duplicate your array which may not be what you want.
db.collection.aggregate([
{ "$project": {
"post": 1, "likes": 1,
"userLiked": { "$setIsSubset": [ [ "userid3" ], "$likes" ] }
}}
])
Finally if your mongod version is 3.0 or older you need to use the $literal operator instead of the [] operator.

Resources