Find n words inside a Mongodb document - mongodb

Im trying to create a query that finds a collection of documents that match a criteria, a simple search system. The problem is that the assets inside the collection are like this:
{
fristName: "foo",
lastName: "bar",
description: "mega foo",
},
{
fristName: "Lorem",
lastName: "Ipsum",
description: "mega Lorem bla bla",
},
If the user wants all the assets that contains the word bar i should show the asset 1 of the example, and that is not a problem, but what if he inputs foo mega?, in that case i need also to show the asset 1 because foo and mega are present in asset 1 (both of them), if he searchs only mega, the output is asset 1 and 2, and if he search mega ipsum, the result is asset 2. I have no idea how to write this query in mongoDb.

Mongodb 2.6+ has built in support for text searches using the $text operator. Here's how to use it.
Build an text index on the desired searchable fields. Note: For MongoDB 2.6 you can only have 1 text index on a collection.
Create text index on one field
db.test.ensureIndex({
"field1" : "text"
}, { name : "Field1TextIndex"});
Create text index on two fields
db.test.ensureIndex({
"field1" : "text",
"field2" : "text"
}, { name : "Field12TextIndex"});
Create text index for any string field
db.test.ensureIndex({
"$**" : "text"
}, { name : "AllTextIndex"});
Query the collection using the $text operator.
Here's the format for $text
{ $text: { $search: <string of keywords>, $language: <string> } }
Example code
Setup
use test;
var createPerson = function(f,l,d){
return { firstName : f, lastName: l, description : d};
};
db.test.remove({});
db.test.insert(createPerson("Ben", "Dover", "Real Estate Agent"));
db.test.insert(createPerson("James", "Bond", "secret agent, ben's friend"));
Creating an text index on all string fields in a document.
db.test.ensureIndex({ "$**" : "text" }, { name : "AllTextIndex"});
Query all fields for keywords
Searching for ben
db.test.find({
$text : {
$search : "ben"
}
});
Output
{ "_id" : "...", "firstName" : "James", "lastName" : "Bond", "description" : "secret agent, ben's friend" }
{ "_id" : "...", "firstName" : "Ben", "lastName" : "Dover", "description" : "Real Estate Agent" }
The search for "ben" returned both documents since one had Ben as the firstName, and the other had ben's in the description field.
Querying for real friend produces the same result.
db.test.find({
$text : {
$search : "real friend"
}
});
More info here:
Doc: Create a text Index on multiple fields
Video: Demo of MongoDB Text Search and Hashed Shard Keys
Doc: $text operator

Possible solution is to search via keywords. I mean you have to add keywords field to each object, like:
{
fristName: "foo",
lastName: "bar",
description: "mega foo",
keywords: ["foo", "bar", "mega"]
},
{
fristName: "Lorem",
lastName: "Ipsum",
description: "mega Lorem bla bla",
keywords: ["mega", "Lorem", "Ipsum", "bla"]
},
The you have to split request string to keywords, e.g.
"foo mega"
will be converted into
["foo", "mega"]
and then you can search objects by keywords field.

Related

Adding multiple key/values

In my database.collection i.e. db.blog.posts I am trying to add a key and value that itself has multiple keys and values.
Current collection:
db.blog.posts.findOne()
"title":"blog posts"
I tried using $set, $push but nothing seems to work.
This also didn't work when I tried adding single collection:
db.blog.posts.updateOne({"title":"blog posts"}, {"$set":{"comments":[{"comment":"good post", "author":"john","votes":0}]}})
Nor insertOne instead of updateOne and I even tried with:
var myEmployee=[
{"comment":"good post", "author":"john", "votes":0},
{"comment":"i thought it was too short", "author":"claire","votes":3},
{"comment":"free watches", "author":"claire","votes":-1},
];
db.blog.posts.insert(myEmployee)
This is what I want:
"title" : "A blog post",
"comments" : [
{
"name" : "joe",
"email" : "joe#example.com",
"content" : "nice post."
},
{
"name" : "bob",
"email" : "bob#example.com",
"content" : "good post."
}
]
The updateOne command you have should have created an array for comments with a single entry. If you wanted multiple entries, you can just add multiple objects to the array in the update. The $set operator will change the value of the key to what you set as the second parameter.
db['blog.posts'].updateOne({"title":"blog posts"}, {
"$set": {
"comments":[
{
"name" : "joe",
"email" : "joe#example.com",
"content" : "nice post."
},
{
"name" : "bob",
"email" : "bob#example.com",
"content" : "good post."
}
]
}
})
If you want to add additional items to the comments, this can be done with $push. The $push operator adds to the array.
db['blog.posts'].updateOne({"title":"blog posts"}, {
"$push": {
"comments": {
"comment": "good post",
"author": "john",
"votes": 0
}
}
})
Docs for $set
Docs for $push
NB the examples above are for a collection named 'blog.posts' rather than a database named 'blog' and a collection names 'posts'. Ideally, brackets should be used for the property accessor where the collection name is not a valid JavaScript identifier although the dot notation in the question still works.

Meteor/MongoDB Reference specific subdocument from another document

I'm working on prototyping a note-taking application in Meteor; functional requirements include:
users have access to shared notes
notes contain distinct sections
each user needs to be able to add notations to notes/sections
notations can be preserved over time (e.g. add to existing notations without updating or deleting previously created notation)
notations should be private between users
Given the above, each document has a data key that contains the array of subdocuments - each section of the note. Something like this:
{
"_id" : ObjectId("someObjectID"),
"owner" : "Q5mpJZnAtFN5EMWT9",
"createdAt" : "2018-01-05T22:56:03.257Z",
"updatedAt" : "2018-01-06T12:07:03.123Z",
"parent" : null,
"title" : "Note Title",
"data" : [
{
"date" : "2018-01-05T22:56:03.257Z",
"title" : "Section 1 Title",
"text" : "Section content goes here..."
},
{
"date" : "2018-01-05T22:56:03.257Z",
"title" : "Section 2 Title",
"text" : "Section content goes here..."
}
]
}
For the main notes documents, the data array stores the sections as subdocuments; for user notations, the data array stores their personal notations as subdocuments. My thinking is to use the parent key to distinguish between shared notes and user notations:
parent : null for "top level", shared notes
something like parent : "yG8xrh6KiZXv7e8MD" to point back to the "top level" note or subdocument for user notations. (Hopefully this makes sense).
Two questions. First and foremost - is this a valid design?
If it IS a valid design, how do I then reference a specific subdocument? For example, in the above document, if a user wants to add a notation to Section 2 only? Can I add an _id to the subdocument and then use that value for the parent key in the notation document?
This not the complete solution, but just an example:
I would do it something like this. I'd modify your document a bit, adding notations field in every section:
{
"_id" : ObjectId("someObjectID"),
"owner" : "Q5mpJZnAtFN5EMWT9",
"createdAt" : "2018-01-05T22:56:03.257Z",
"updatedAt" : "2018-01-06T12:07:03.123Z",
"parent" : null,
"title" : "Note Title",
"data" : [
{
"date" : "2018-01-05T22:56:03.257Z",
"title" : "Section 1 Title",
"text" : "Section content goes here...",
"notations": [
{
_id: "some id",
version:1
userId: "fsajksffhj",
date: "2018-01-05T22:56:06",
note: "some note about this sectioon"
},
{
_id: "some id2",
version:1,
userId: "fsajksffhj",
date: "2018-01-05T22:56:06",
note: "some note about this sectioon"
},
{
_id: "some id1",
version:1,
userId: "fsajksffhj",
date: "2018-02-06T00:56:06",
note: "edited the first notation"
}
]
},
{
"date" : "2018-01-05T22:56:03.257Z",
"title" : "Section 2 Title",
"text" : "Section content goes here..."
}
]
}
notations should be private between users
This is harder part. I'd use Meteor Methods to do this. Another way could be to use MongoDB's aggregation functionality with match, unwind, re-match, group and create document again. You are using reactivity if using either of these.
Meteor.methods({
'notes.singleNote: function(noteId, notationsUserId) {
check(noteId, String);
check(notationsUserId);
let note = Notes.findOne(noteId);
// remove other users' notations
note.data = note.data.map(function(data) {
if (data.notations) {
data.notations = data.notations.filter(function(d) {
return d.userId === notationsUserId;
});
}
return data
});
});
return note;
}
});

Perform a search on main collection field and array of objects simultaneously

I have my document structure as below:
{
"codeId" : 8.7628945723895E13, // long numeric value stored in scientific notation by Mongodb
"problemName" : "Hardware Problem",
"problemErrorCode" : "97695686856",
"status" : "active",
"problemDescription" : "ghdsojgnhsdjgh sdojghsdjoghdghd i0dhgjodshgddsgsdsdfghsdfg",
"subProblems" : [
{
"codeId" : 8.76289457238896E14,
"problemName" : "Some problem",
"problemErrorCode" : "57790389503490249640",
"problemDescription" : "This is edited",
"status" : "active",
"_id" : ObjectId("589476eeae39b20b1c15535b")
},
...
]
}
I have a search field which should search by codeId which basically serves as parentCodeID in search fields as shown below
Now, along with parentIdCode I want to search for codeId, problemCode, problemName and problemDescription as well.
How do I query the submodules with a regex search and at same time tag some parent field with "$or" clause etc. to achieve this ?
You can try something like this.
query = {
'$or': [{
"codeId":somevalue
}, {
"subProblems.codeId": {
"$regex": searchValue,
"$options": "i"
}
}, {
//rest of sub modules fields
}]
};

How to query nested objects with multiple value in MongoDB?

I have array of nested objects in my MongoDB schema. Lets say,
var Post = {
"title": "Some title",
"advertisers" : [
{
"category" : "Tech",
"name" : "Henry",
"city" : "New york",
"updated_at" : ISODate("2011-07-26T21:02:19Z"),
"created_at" : ISODate("2011-07-26T21:02:19Z")
},
]
}
I want to find the post based on the multiple values in nested schema. eg
Post.find({ 'advertisers.category' : 'Tech', 'advertisers.city': 'New York' });
It returns post which matches one of the above criteria. But i want posts which matches both.
How can i do it?
You can use the $elemMatch operator to do this:
Post.find({ advertisers: {$elemMatch: {category: 'Tech', city: 'New York' }}})

How to do query on multiple nested data fields in MongoDB

So, what I'm trying to do is query all documents that have a City of 'Paris' and a State of 'France'. I need to do some kind of join, but I haven't been able to figure out how to construct it.
I'm using the c# driver, but I'll gladly accept help using any method.
{
"_id" : ObjectId("519b407f3c22a73a7c29269f"),
"DocumentID" : "1",
"Meta" : [{
"Name" : "City",
"Value" : "Paris",
}, {
"Name" : "State",
"Value" : "France",
}
}]
}
{
"_id" : ObjectId("519b407f3c22a73a7c29269g"),
"DocumentID" : "2",
"Meta" : [{
"Name" : "City",
"Value" : "Paris",
}, {
"Name" : "State",
"Value" : "Texas",
}
}]
}
The $elemMatch operator is used to indicate that all the conditions within it must be matched by the same array element. So (to switch to shell syntax) to match all documents which have meta city Paris you would do
db.collection.find( {Meta:{$elemMatch:{Name:"City",Value:"Paris"}}} )
This assures you won't match something which has Name: "somethingelse", Value: "Paris" somewhere in its array with a different array element matching the Name:"City".
Now, default combination for combining query conditions is "and" so you can continue adding attributes:
db.collection.find( {Meta: {
$elemMatch:{Name:"City",Value:"Paris"},
$elemMatch:{Name:"State",Value:"France"}
}
}
)
Now if you want to add another condition you keep adding it but if you want a NOT then you do it like this:
db.collection.find( {Meta: {
$elemMatch:{Name:"City",Value:"Paris"},
$elemMatch:{Name:"State",Value:"France"},
$not: {$elemMatch:{Name:"Arrondissement",Value:"Louvre"}}
}
}
)
I might be answering my own question here, but I'm new to MongoDB, so while this appears to give me the results I'm after, it might not be the optimum approach.
var result = collection.Find(
Query.And(
Query.ElemMatch("Meta", Query.EQ("Name", "City")),
Query.ElemMatch("Meta", Query.EQ("Value", "Paris")),
Query.ElemMatch("Meta", Query.EQ("Name", "State")),
Query.ElemMatch("Meta", Query.EQ("Value", "France")))
);
Which leads to a follow up - how would I get all of the documents whose 'City' is 'Paris' and 'State' is 'France' but whose 'Arrondissement' is not 'Louvre'?