MongoDB query on multiple array of objects - node.js

Say I have a mongoSchema of user with the following field
googleId: String,
name: String,
Language : [{}],
User can initially specify an array of language-set [up to 5] on their profile page.
For example, user can have 2 language set as below:
Language :
[main: {name: English, code: ko}, secondary: [{name:Japanese, code: jp}, {name:Chinese, code: en}]],
[main: {name: Korean, code: ko}, secondary: [{name:Japanese, code: jp}, {name:English, code: en}]
From this information, I want to render out only the post that matches the following preference.
For example post with English to Japanese, English to Chinese (from first language set)
as well as post with Korean to Japanese, Korean to English (from second language set)
My post schema has
postName: String
original: {},
target: {},
For example,
postName: 'Korean to Japanese Post'
original: {name: Korean, code: ko}
target: {name: Japanese, code jp}
What should I put inside the curly brackets to fetch the posts with specified language set
const prefLang = req.user.Language
const post = await Post.find({ some query using prefLang to check over matching original and target })
res.send(translations)

The query object should look like this for your example
{
$or: [
{
original: { name: "English", code: "en" },
target: {
$in: [{ name: "Japanese", code: "jp" }, { name: "Chinese", code: "cn" }]
}
},
{
original: { name: "Korean", code: "ko" },
target: {
$in: [{ name: "Japanese", code: "jp" }, { name: "English", code: "en" }]
}
}
]
}
So you will have to construct the condition from prefLang like this
const query = {
$or: perfLang.map(lang => ({
original: lang.main,
target: { $in: lang.secondary }
}))
}
const post = await Post.find(query)
Tip:
If the language objects have a strict schema, you can define them like this
const LanguageSchema = new mongoose.Schema({
name: String,
code: String
}, {
_id: false
})
const UserSchema = new mongoose.Schema({
//...
googleId: String,
name: String,
Language: [{ main: LanguageSchema, secondary: [LanguageSchema] }]
})

Sounds like the usage of basic boolean logic would suffice:
const prefLang = req.user.Language;
let conditions = [];
prefLang.forEach((langObj) => {
let tragetLangs = langObj.secondary.map(lang => lang.code);
conditions.push({
$and: [
{
"original.code": langObj.main.code,
},
{
"target.code": {$in: tragetLangs}
}
]
})
})
// you should check conditions !== empty array.
const post = await Post.find({$or: conditions})

Related

Convert DB response from object to array

I have the following schema:
const mySchema = new mongoose.Schema({
name: String,
subscribers: [ { name: String } ]
})
const myModel = mongoose.model('User', mySchema, 'users')
and I have this code in one of my controllers:
const allOfHisSubscribers = await myModel.findById(req.params.id).select('subscribers')
I want the database response of the await myModel.findByid call to be:
[
{ name: 'x' },
{ name: 'y' },
{ name: 'z' },
]
However, the code above is returning:
{
subscribers: [
{ name: 'x' },
{ name: 'y' },
{ name: 'z' },
]
}
I don't want the JavaScript way of doing it, I know you can use something like Array.prototype.map to get the result, but I am using a middleware, so I can only use the mongoose or MongoDB way of doing it (if possible).
Got it :D, if not, let me know in the comments 🌹

MongoDB: add object to subarray if not exists

I searched many questions here and other articles on the web, but they all seem to describe somehow different cases from what I have at hand.
I have User schema:
{
username: { type: String },
lessons: [
{
lesson: { type: String },
result: { type: String }
}
]
}
I want to add new element into lessons or skip, if there is already one with same values, therefore I use addToSet:
const dbUser = await User.findOne({ username })
dbUser.lessons.addToSet({ lesson, result: JSON.stringify(result) })
await dbUser.save()
However it makes what seems to be duplicates:
// first run
[
{
_id: 60c80418f2bcfe5fb8f501c1,
lesson: '60c79d81cf1f57221c05fdac',
result: '{"correct":2,"total":2}'
}
]
// second run
[
{
_id: 60c80418f2bcfe5fb8f501c1,
lesson: '60c79d81cf1f57221c05fdac',
result: '{"correct":2,"total":2}'
},
{
_id: 60c80470f2bcfe5fb8f501c2,
lesson: '60c79d81cf1f57221c05fdac',
result: '{"correct":2,"total":2}'
}
]
At this point I see that it adds _id and thus treats them as different entries (while they are identical).
What is my mistake and what should I do in order to fix it? I can change lessons structure or change query - whatever is easier to implement.
You can create sub-documents avoid _id. Just add _id: false to your subdocument declaration.
const userSchema = new Schema({
username: { type: String },
lessons: [
{
_id: false,
lesson: { type: String },
result: { type: String }
}
]
});
This will prevent the creation of an _id field in your subdoc, and you can add a new element to the lesson or skip it with the addToSet operator as you did.

mongodb, check if the key exist while appending to an array

Note: checking if the key books exist or not, creating if not and than updating it.
I am using mongodb driver with nodejs.
In the db.collection('userData')The document looks like this:
{
user_id: 'user1',
books: [{
id: 'book1',
title: 'this is book1'
},
{
id: 'book1',
title: 'this is book1'
}]
}
when inserting a new book entry, how to check if the array of books exists in the document, if not then add a key books in the document and then insert the book entry.
You have to do 2 separate queries,
Find user document
Check condition if books field present
If Present then push object, else set new field
var user_id = "user1";
var bookData = { id: 'book1', title: 'this is book1' };
// FIND USER DATA
var userData = await db.collection('userData').findOne({ user_id: user_id }, { books: 1 });
var updateBody = { $push: { books: bookData } };
// IF BOOKS FIELD NOT PRESENT THEN SET NEW
if (!userData.books) {
updateBody = { $set: { books: [bookData] } };
}
var updateData = await db.collection('userData').updateOne({ user_id: user_id }, updateBody);
console.log(updateData);
Second option you can use update with aggregation pipeline starting from MongoDB 4.2,
$ifNull check is field is null then return []
$concatArrays to concat current books with new book object
var bookData = { id: 'book1', title: 'this is book1' };
db.collection('userData').update({
// put your condition
},
[{
$set: {
books: {
$concatArrays: [
{ $ifNull: ["$books", []] },
[bookData]
]
}
}
}],
{ multi: true }
);
Playground

mongoose find object into array of object

I'm new in mongoose and I'm trying to find user by code [user.test.test1.code] , any idea ?
Model :
const userSechema = new mongoose.Schema({
name: {
type: String,
required: true
},
test: [{}],
})
Data :
{
"_id": {
"$oid": "600020ab34742c2d34ae45e5"
},
"test": [{
"test1": {
"code": 11111
},
"test2": {
"code": 22222
}
}]
"name": "daniel"
}
query :
let regex = new RegExp(req.query.searchUserKey, 'i')
const users = await User.find({ $or: [{'name': regex },{'test.test1': { code : regex} }]})
-- Solution --
Thanks you guys, both answers is work for me
Is as simple as do "test.test1.code": 418816 into find query like this:
db.collection.find({
"test.test1.code": 418816
})
This query will give you all documents where exists test.test1.code with value 418816.
Note that this query return the whole document, not only the sub-document into the array. But I'm assuming by your post that a user is the document where exists the field name.
Example here
you can use $elemMatch, check the documentation
const users = await User.find(
{ test: { $elemMatch: { "test1.code": 418816 } } }
)

elasticsearch search text return full array issue

I am using mongoosastic for elasticsearch. and i done all setup and its working fine. but problem is result are not getting properly.
FILE:- mongoose and mongoosastic.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var medicineSchema = require('./search')
var mongoosastic = require("mongoosastic");
var UserProfileSchema = new Schema({
userId: String,
username: String,
address: String,
number: Number,
task: [{
name: {
type: String,
es_boost: 2.0 // or es_indexed:true
},
taskCode: String,
}]
});
UserProfileSchema.plugin(mongoosastic);
UserProfileSchema.plugin(mongoosastic, {
host: "localhost",
port: 9200,
// ,curlDebug: true
});
UserProfile = module.exports = mongoose.model('UserProfile', UserProfileSchema);
UserProfile.createMapping(function(err, mapping) {
if (err) {
console.log('error creating mapping (you can safely ignore this)');
console.log(err);
} else {
console.log('mapping created!');
console.log(mapping);
}
});
And my search Query:
var UserProfileSchema = require('../../app/models/user');
UserProfileSchema.search({
query_string: {
query: name
}
}, function(err, result) {
if (err) {
callback({
RESULT_CODE: '-1',
MESSAGE: 'System error'
});
} else {
callback({
RESULT_CODE: '1',
DATA: result
});
}
});
Now my problem is if task array has 3 object and when i search for task string i.e "abc" it will return full collection. with all task But i want only searched string object from task array. i.e name :abc object
......
"task" [{
name: 'abc',
taskCode: 123
},{
name: 'xyz',
taskCode: 123
},{
name: 'cdx',
taskCode: 123
}]
The good thing is that your task field is already of type nested in your schema, which is a pre-condition for achieving what you expect.
Now in order to achieve what you want you need to use inner_hits in your query.
UserProfileSchema.search({
"query": {
"nested": {
"path": "task",
"query": {
"match": {
"task.name": name
}
},
"inner_hits": {} <--- this does the magic
}
}
}, ...

Resources