Fidning a complete term among different fields for Atlas Search - mongodb-atlas

I am trying to use Atlas search to search for a name. In my dataset name is divided between FirstName and LastName, and that causes the problem for me.
I have tried the following stage in my aggregation:
{
"$search" : {
"phrase" : {
"query" : "John Doe",
"path" : ["FirstName", "LastName"]
}
}
},
This does not find any results even if I have a document with FirstName = "John" and LastName = "Doe".
If I search for John or Doe, I get this result. I have also tried to use $text instead of Phrase, and even if i then get John Doe when I do that search I also get any other document that has FirstName = John or LastName = Doe, so I get a lot more hits than I want.
So basically I want my search to return "John Doe", "John William Doe", but not "Frank Doe" or "John Williams".
I really don't want to introduce a new field in the database for this just to combine the FirstName and LastName, so hopefully someone has a nice solution to this!

Maybe you could split user input on spaces and use the compound operator with two must clauses. Here's what the above query should look like:
"must": [
{
"test": {
"query": "John",
"path": "FirstName"
}
},
{
"text": {
"query": "Doe",
"path": "LastName"
}
}
],
"minimumShouldMatch": 2 // specifying that we need both fields to match
}

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.

How to update a one to many relationship MongoDB

If i have a Person collection that has many addresses, how can I insert multiple addresses that belong to a person? I know how to do it if it were a one to one relationship where I would simply do something like:
db.persons.update({_id: '12345'}, {$set: {'address': '12345 fake st'}})
However, that won't work for a one to many relationship since the person's address would get replaced every time someone adds an address to that person. Can someone help? Thank you in advance.
If you want a person to hold many addresses then you could make address an array and you could insert new addresses using the push operator.
db.persons.update(
{ "_id": "12345" },
{ "$push": { "address": "12345 fake st"}}
);
Your schema would then look like this:
{
"_id" : "12345",
"address" : [
"12345 fake st",
"6789 second st",
...
]
}
Of course you can make this as complex as required. So if you need to store extra information for each address, you can instead insert subdocuments into the address array.
{
"_id" : "12345",
"address" : [
{
"number" : "6789",
"street" : "second st",
"primary" : false
},
{
"number" : "12345",
"street" : "fake st",
"primary" : true
},
...
]
}
As noted in the comments, if you want to ensure that there are no duplicates in your array, you should use the $addToSet operator, which provides that functionality.

Find n words inside a Mongodb document

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.

Can I utilize indexes when querying by MongoDB subdocument without known field names?

I have a document structure like follows:
{
"_id": ...,
"name": "Document name",
"properties": {
"prop1": "something",
"2ndprop": "other_prop",
"other3": ["tag1", "tag2"],
}
}
I can't know the actual field names in properties subdocument (they are given by the application user), so I can't create indexes like properties.prop1. Neither can I know the structure of the field values, they can be single value, embedded document or array.
Is there any practical way to do performant queries to the collection with this kind of schema design?
One option that came to my mind is to add a new field to the document, index it and set used field names per document into this field.
{
"_id": ...,
"name": "Document name",
"properties": {
"prop1": "something",
"2ndprop": "other_prop",
"other3": ["tag1", "tag2"],
},
"property_fields": ["prop1", "2ndprop", "other3"]
}
Now I could first run query against property_fields field and after that let MongoDB scan through the found documents to see whether properties.prop1 contains the required value. This is definitely slower, but could be viable.
One way of dealing with this is to use schema like below.
{
"name" : "Document name",
"properties" : [
{
"k" : "prop1",
"v" : "something"
},
{
"k" : "2ndprop",
"v" : "other_prop"
},
{
"k" : "other3",
"v" : "tag1"
},
{
"k" : "other3",
"v" : "tag2"
}
]
}
Then you can index "properties.k" and "properties.v" for example like this:
db.foo.ensureIndex({"properties.k": 1, "properties.v": 1})

mongoDB concatenate results

Whats the best way to concatenate results in MongoDB? In particular the PHP driver? Do I need to use mapReduce?
In mySQL I would do something like this: SELECT CONCAT(fname,' ',lname) as name FROM users but I can't seem to find a simple way to do this in mongo.
In the PHP Driver
I recommend doing this in your application. Use PHP's concatenation features to add a "fullname" attribute/key to the user object/array. I'd stay away from map/reduce/finalize unless you need to do some database-side processing or selecting before returning the results. (And, before that, maybe look into where queries as well - http://www.mongodb.org/display/DOCS/Advanced+Queries.)
In the Shell
If this is a one-off query and you're doing it in the shell, there are two different (but related) ways to go about this.
Which one you use depends widely on if only want the concat-ed name or if you want the rest of the document to go with it. For instance, if you only want the name, you can do something like this:
> db.show_concat.find().forEach( function (o) { print(o.fname + ' ' + o.lname); } )
john smith
jane doe
Otherwise, if you want the other fields, you can do:
> db.show_concat.find().forEach( function (o) { o.full_name = o.fname + ' ' + o.lname; printjson(o); } )
{
"_id" : ObjectId("4cd6dabb5391d08d405bb0bb"),
"fname" : "john",
"lname" : "smith",
"full_name" : "john smith"
}
{
"_id" : ObjectId("4cd6daee5391d08d405bb0bc"),
"fname" : "jane",
"lname" : "doe",
"full_name" : "jane doe"
}
You can use aggregate, $project and $concat :
https://docs.mongodb.org/v3.0/reference/operator/aggregation/project/
https://docs.mongodb.org/manual/reference/operator/aggregation/concat/
It would be something like this :
db.show_concat.aggregate(
[
{ $project: { full_name: { $concat: [ "$fname", " - ", "$lname" ] } } }
]
)