MongoDB Project - return data only if $elemMatch Exist - mongodb

Hello Good Developers,
I am facing a situation in MongoDB where I've JSON Data like this
[{
"id": "GLOBAL_EDUCATION",
"general_name": "GLOBAL_EDUCATION",
"display_name": "GLOBAL_EDUCATION",
"profile_section_id": 0,
"translated": [
{
"con_lang": "US-EN",
"country_code": "US",
"language_code": "EN",
"text": "What is the highest level of education you have completed?",
"hint": null
},
{
"con_lang": "US-ES",
"country_code": "US",
"language_code": "ES",
"text": "\u00bfCu\u00e1l es su nivel de educaci\u00f3n?",
"hint": null
}...
{
....
}
]
I am projecting result using the following query :
db.collection.find({
},
{
_id: 0,
id: 1,
general_name: 1,
translated: {
$elemMatch: {
con_lang: "US-EN"
}
}
})
here's a fiddle for the same: https://mongoplayground.net/p/I99ZXBfXIut
I want those records who don't match $elemMatch don't get returned at all.
In the fiddle output, you can see that the second item doesn't have translated attribute, In this case, I don't want the second Item at all to be returned.
I am using Laravel as Backend Tech, I can filter out those records using PHP, but there are lots of records returned, and I think filtering using PHP is not the best option.

You need to use $elemMatch in the first parameter
db.collection.find({
translated: {
$elemMatch: {
con_lang: "IT-EN"
}
}
})
MongoPlayground

Related

Mongodb find documents with a specific aggregate value in an array

I have a mongo database with a collection of countries.
One property (currencies) contains an array of currencies.
A currency has multiple properties:
"currencies": [{
"code": "EUR",
"name": "Euro",
"symbol": "€"
}],
I wish to select all countries who use Euro's besides other currencies.
I'm using the following statement:
db.countries.find({currencies: { $in: [{code: "EUR"}]}})
Unfortunately I'm getting an empty result set.
When I use:
db.countries.find({"currencies.code": "EUR"})
I do get results. Why is the first query not working and the second one succesfull?
The first query is not working as it checks whether the whole currency array is in the array, which is never true.
It is true when:
currencies: {
$in: [
[{
"code": "EUR",
"name": "Euro",
"symbol": "€"
}],
...
]
}
I believe that $elemMatch is what you need besides the dot notation.
db.collection.find({
currencies: {
$elemMatch: {
code: "EUR"
}
}
})
Sample Mongo Playground
MongoDB works in the same way if the query field is an array or a single value, that's why the second one works.
So why the first one doesn't work? The problem here is that you are looking for an object that is exactly defined as {code: "EUR"}: no name or symbol field are specified. To make it work, you should change it to:
db.getCollection('countries').find({currencies: { $in: [{
"code" : "EUR",
"name" : "Euro",
"symbol" : "€"
}]}})
or query the subfield directly:
db.getCollection('stuff').find({"currencies.code": { $in: ["EUR"]}})

Search and update in array of objects MongoDB

I have a collection in MongoDB containing search history of a user where each document is stored like:
"_id": "user1"
searchHistory: {
"product1": [
{
"timestamp": 1623482432,
"query": {
"query": "chocolate",
"qty": 2
}
},
{
"timestamp": 1623481234,
"query": {
"query": "lindor",
"qty": 4
}
},
],
"product2": [
{
"timestamp": 1623473622,
"query": {
"query": "table",
"qty": 1
}
},
{
"timestamp": 1623438232,
"query": {
"query": "ike",
"qty": 1
}
},
]
}
Here _id of document acts like a foreign key to the user document in another collection.
I have backend running on nodejs and this function is used to store a new search history in the record.
exports.updateUserSearchCount = function (userId, productId, searchDetails) {
let addToSetData = {}
let key = `searchHistory.${productId}`
addToSetData[key] = { "timestamp": new Date().getTime(), "query": searchDetails }
return client.db("mydb").collection("userSearchHistory").updateOne({ "_id": userId }, { "$addToSet": addToSetData }, { upsert: true }, async (err, res) => {
})
}
Now, I want to get search history of a user based on query only using the db.find().
I want something like this:
db.find({"_id": "user1", "searchHistory.somewildcard.query": "some query"})
I need a wildcard which will replace ".somewildcard." to search in all products searched.
I saw a suggestion that we should store document like:
"_id": "user1"
searchHistory: [
{
"key": "product1",
"value": [
{
"timestamp": 1623482432,
"query": {
"query": "chocolate",
"qty": 2
}
}
]
}
]
However if I store document like this, then adding search history to existing document becomes a tideous and confusing task.
What should I do?
It's always a bad idea to save values are keys, for this exact reason you're facing. It heavily limits querying that field, obviously the trade off is that it makes updates much easier.
I personally recommend you do not save these searches in nested form at all, this will cause you scaling issues quite quickly, assuming these fields are indexed you will start seeing performance issues when the arrays get's too large ( few hundred searches ).
So my personal recommendation is for you to save it in a new collection like so:
{
"user_id": "1",
"key": "product1",
"timestamp": 1623482432,
"query": {
"query": "chocolate",
"qty": 2
}
}
Now querying a specific user or a specific product or even a query substring is all very easily supported by creating some basic indexes. an "update" in this case would just be to insert a new document which is also much faster.
If you still prefer to keep the nested structure, then I recommend you do switch to the recommended structure you posted, as you mentioned updates will become slightly more tedious, but you can still do it quite easily using arrayFilters for updating a specific element or just using $push for adding a new search

Update a nested field with an unknown index and without affecting other entries

I have a collection with a layout that looks something like this:
student1 = {
"First_Name": "John",
"Last_Name": "Doe",
"Courses": [
{
"Course_Id": 123,
"Course_Name": "Computer Science",
"Has_Chosen_Modules": false
},
{
"Course_Id": 284,
"Course_Name": "Mathematics",
"Has_Chosen_Modules": false
}
]
};
I also have the following update query:
db.Collection_Student.update(
{
$and: [
{First_Name: "John"},
{Last_Name: "Doe"}
]
},
{
$set : { "Courses.0.Has_Chosen_Modules" : true }
}
);
This code will currently update the Computer Science Has_Chosen_Modules value to true since the index is hardcoded. However, what if I wanted to update the value of Has_Chosen_Modules via the Course_Id instead (as the course might not necessarily be at the same index every time)? How would I achieve this without it affecting the other courses that a given student is taking?
You can select any item in the sub array of your document by targeting any property in the sub array of your document by using dot .
You can easily achieve this by the following query.
db.Collection_Student.update(
{
First_Name: "John",
Last_Name: "Doe",
'Courses.Course_Id': 123
},
{
$set : { "Courses.$.Has_Chosen_Modules" : true }
}
);
Conditions in search filter are by default treated as $and operator, so you don't need to specifically write $and for this simple query.

Cannot add _id field to mongo subdocument in Mlab

In my Mlab mongo 3.2 database I have a collection that looks like this:
{
"_id": {
"$oid": "5752d....87985"
},
"name": "...etation",
"description": null,
"user_id": ".....",
"questions": [
{
"prompt": "The conclusions drawn seemed clear to most researchers, however, others were unconvinced, arguing that everything is open to ____________.",
"answer": "interpretation",
"created_at": "2014-11-09T14:59:38.154",
"updated_at": "2014-11-09T14:59:38.154",
"filled_answer": null
},
{
"id": 922,
"prompt": "His existential quest for Truth is in fact the key to his understanding and ____________ of the Bhagavad-Gītā.",
"answer": "interpretation",
"created_at": "2014-10-03T08:07:40.295",
"updated_at": "2014-10-03T08:07:40.295",
"filled_answer": null
},
}
There are two problems with the questions subdocument that I am struggling with:
Sometimes but not always there is a legacy "id" field that I want to $unset but my query is not working.
I want to add an _id ObjectID field where they do not already exist. Currently some have them and some don't.
I have tried a number of queries but none seem to work. For example:
db.droplets.updateMany({"questions.$._id": { $exists: false }},{ $set: {"questions.$._id": new ObjectId()}},{"multi": true, "upsert": true})
Mongo tells me "The positional operator did not find the match needed from the query"
Update
I have successfully found a way to delete all the questions using the following script:
db.droplets4.find().forEach(function (doc) {
doc.questions.forEach(function (question) {
if (question.id) {
delete question.id
}
});
db.droplets.save(doc);
});
But the same strategy is not working for adding Object IDs. This code does not work:
db.droplets4.find().forEach(function (doc) {
doc.questions.forEach(function (question) {
if (!question._id) { question._id = new ObjectId() }
});
db.droplets.save(doc);
});
This should work fine for you
db.droplets4.updateMany( {
"questions._id" : null
},{ $set: {"questions.$._id": new ObjectId()}},{"multi": true, "upsert": true})

MongoDB different query styles with different results

I have a document :
{
"_id": ObjectId("5324d5b30cf2df0b84436141"),
"value": 0,
"metaId": {
"uuid": "8df088b2-9aa1-400a-8766-3080a6206ed1",
"domain": "domain1"
}
}
Also I have ensured indexes of this type:
ensureIndex({"metaId.uuid" : 1})
Now here comes two queries:
db.test.find({"metaId" : {"uuid" : "8df088b2-9aa1-400a-8766-3080a6206ed1"}}).explain()
"cursor" : "BasicCursor"
NO Index used!
db.test.find({"metaId.uuid" : "8df088b2-9aa1-400a-8766-3080a6206ed1"}).explain()
"cursor" : "BtreeCursor metaId.uuid_1"
Index used!
Is there a way to make both queries use index ?
Firstly, the following document:
{
"_id": ObjectId("5324d5b30cf2df0b84436141"),
"value": 0,
"metaId": {
"uuid": "8df088b2-9aa1-400a-8766-3080a6206ed1",
"domain": "domain1"
}
}
Would not match the Query:
db.test.find({
"metaId": {
"uuid": "8df088b2-9aa1-400a-8766-3080a6206ed1"
}
});
Because, it's querying by the value of "metaId" which has to match exactly to:
{
"uuid": "8df088b2-9aa1-400a-8766-3080a6206ed1",
"domain": "domain1"
}
In this case, you'd be using the index on "metaId".
There is a known issue on this, SERVER-2953. You can vote that up if you wish.
In the meantime you could do this instead:
{
"value": 0,
"metaId": [{
"uuid": "8df088b2-9aa1-400a-8766-3080a6206ed1",
"domain": "domain1"
}]
}
And with a slightly different query form then the index will be selected:
db.test.find(
{"metaId" : {
"$elemMatch": {
"uuid" : "8df088b2-9aa1-400a-8766-3080a6206ed1"
}
}}
).explain()
And actually that query will match the index with your current data form as well. However it will not return results. But with the data in this form it will return a match.
It is generally better to use an array element with a "contained" sub-document, even if it is only one. This allows for much more flexible searching, especially if you want to expand on the different field keys in the sub-document in the future.