Is it possible to have $and operator on multiple $text index search in mongo?
I have documents in tp collection of my db
> db.tp.find()
{ "_id" : ObjectId("...."), "name" : "tp", "dict" : { "item1" : "random", "item2" : "some" } }
{ "_id" : ObjectId("...."), "name" : "tp", "dict" : { "item3" : "rom", "item4" : "tttt" } }
Then I do
> db.tp.createIndex({ "$**": "text" })
> db.tp.find({ $and: [{$text : { $search: "random" } }, {$text : { $search: "redruth" } }]})
And it fails with
Error: error: {
"waitedMS" : NumberLong(0),
"ok" : 0,
"errmsg" : "Too many text expressions",
"code" : 2
}
but text index search works for single search so is it not possible to bind multiple text searches with $and operator? By the way I am using wildcard character $** for indexing because I want to search over entire document.
Base on mongoDB docs, AND operator can use directly in search term by combining quote and space. For example, we search for "ssl certificate" AND "authority key", so the query should like:
> db.tp.find({'$text': {'$search': '"ssl certificate" "authority key"'}})
A query can specify at most one $text expression. See:
https://docs.mongodb.com/manual/reference/operator/query/text/
Related
Here is the model 'Class' model for which I have created the "text" index for 'keywords','lifeArea',''type'.
Structure of the model:
{
"_id" : ObjectId("558cf6e3387419850d892712"),
"keywords" : "rama,seetha",
"lifeArea" : [
"Emotional Wellness"
],
"type" : "Pre Recorded Class",
"description" : "ram description",
"synopsis" : "ram syn",
"name" : "ram demo",
"__v" : 0
}
db.Class.getIndexes()
// displaying index
{
"v" : 1,
"key" : {
"_fts" : "text",
"_ftsx" : 1
},
"name" : "classIndex",
"ns" : "innrme.classes",
"weights" : {
"keywords" : 1,
"lifeArea" : 1,
"type" : 1
},
"default_language" : "english",
"language_override" : "language",
"textIndexVersion" : 2
}
I want to do a text search on the fields mentioned above. I tried the following query.
db.classes.find({$or:[{keywords: { $text: { $search: "rama abc" } } }, {type: {$text: { $search: "class" }}}],score: {$meta: 'textScore'}});
But it did not work and I got the follwing error
Error: error: {
"$err" : "Can't canonicalize query: BadValue unknown operator: $text",
"code" : 17287
}
Please help me to get the correct query.
Please correct/educate me if I am wrong in asking the question or in explaining the problem
That actual error suggests your mongodb is a version less than 2.6 ( so no text search in that way ). But you cannot do that anyway for two reasons.
An $or expression can only have one special index expression, being either "text" or "geospatial" in the arguments.
You are expecting text searches on "two" different fields and you can only have one text index per collection. However that single index can be spread over several fields in the document. But you cannot ask different search terms for different fields.
Documentation quote:
You cannot combine the $text expression, which requires a special text index, with a query operator that requires a different type of special index. For example you cannot combine $text expression with the $near operator.
And it should also say "You cannot use $or with a $text expression or the $near operator where either are used in more than one condition." But that little piece of information is missing, but you still cannot do it.
Your syntax is generally not correct, but even with the correct syntax in a supported version of MongoDB you would get an error trying to use $or like this:
Error: error: {
"$err" : "Can't canonicalize query: BadValue Too many text expressions",
"code" : 17287
}
So to resolve this you need:
To have a MongoDB server version of 2.6 or greater that supports the $text syntax ( or live with command forms )
To live with indexing over multiple fields and using a single index.
To execute "separate queries" in place of your "or" conditions and "combine" the results in your client API interface.
That is the only way you get "or" conditions like this with MongoDB text search.
First of all I don't think you can use $text in that manner, you need first to create a text index on the collection then you can use it but without specifying any field because it works on indexes not fields.
Please check here: http://docs.mongodb.org/manual/administration/indexes-text/
I have a collection of persons whose schema looks like the collection of following documents.
Document: {
name:
age:
educations:[{
title:xyz,
passed_year:2005,
univercity:abc},
{
title:asd
passed_year:2007,
univercity:mno
}],
current_city:ghi
}
Now I wanna show all the persons who has not done xyz education from abc university in year 2005.
I think two possible queries for this need but not sure which one to use as both of them are giving me the output
Query 1:
db.persons.find({"education":{$ne:{$elemMatch:{"title":"xyz","passed_year":2005,"univercity":"abc"}}}})
Query 2:
db.persons.find({"education":{$not:{$elemMatch:{"title":"xyz","passed_year":2005,"univercity":"abc"}}}})
I'm quite confused about operator $ne and $not, which one should I use with $elemMatch as both of them are giving me the output.
Given this $elemMatch: {"title":"xyz","passed_year":2005,"univercity":"abc"} I think you want to exclude any documents which contain an sub document in the educations array which contains all of these pairs:
"title" : "xyz"
"passed_year" : 2005
"univercity" : "abc"
This query will achieve that:
db.persons.find({
"educations": {
$not: {
$elemMatch:{"title": "xyz", "passed_year": 2005, "univercity": "abc"}
}
}
})
In your question you wrote:
both of them are giving me the output
I suspect this is because your query is specifying education whereas the correct attribute name is educations. By specifying education you are adding a predicate which cannot be evaluated since it references a non existent document attribute so regardless of whether that predicate uses $ne or $not it will simply not be applied.
In answer to the question of which operator to use: $not or $ne: if you run the above query with .explain(true) you'll notice that the parsed query produced by Mongo is very different for each of these operators.
Using $ne
"parsedQuery" : {
"$not" : {
"educations" : {
"$eq" : {
"$elemMatch" : {
"title" : "xyz",
"passed_year" : 2005,
"univercity" : "abc"
}
}
}
}
}
Using $not:
"parsedQuery" : {
"$not" : {
"educations" : {
"$elemMatch" : {
"$and" : [
{
"passed_year" : {
"$eq" : 2005
}
},
{
"title" : {
"$eq" : "xyz"
}
},
{
"univercity" : {
"$eq" : "abc"
}
}
]
}
}
}
}
So, it looks like use of $ne causes Mongo to do something like this psuedo code ...
not educations equalTo "$elemMatch" : {"title" : "xyz", "passed_year" : 2005, "univercity" : "abc"}
... i.e. it treats the elemMatch clause as if it is the RHS of an equality operation whereas use of $not causes Mongo to actually evaluate the elemMatch clause.
Have records in my db with such structure:
{
"_id" : "YA14163134",
"discount" : "",
"retail" : "115.0000",
"cost" : "",
"description" : "Caterpillar Mens Big Twist Analog Watch",
"stock_update" : "05",
"brand" : "Kronos",
"img_url" : "image2342000.jpg",
"UPC" : "4895053708012",
"stock" : [ [ "1611292138", "5" ], [ "1612032232", "4" ], [ "1612050918", "0" ] ]
}
and looking for query to get all records that have in "stock" "1612050918" value. That is update id.
Trying something like:
db.vlc.find({stock: {$elemMatch:{$all:["1612050918"]}}})
or
db.vlc.find({stock: { $in : ['1611292138']}})
or
db.vlc.find({stock: { $all : [[1611292138]]}})
with no result. It works only if I include in request second array element like here
db.vlc.find({stock: { $all : [['1611292138', '7']]}})
but that limit my request to all items from update with qnty 7 when I need with any qnty. Thank you in advance!
use this query:
{
"stock" : {
"$elemMatch" : {
"$elemMatch" : {
"$eq" : "1611292138"
}
}
}
}
Explanation:
The first $elemMatch allows you to scan all three arrays under stock
The nex $elemMatch allows you to scan the two elements in the sub-arrays
since $elemMatch requires a query object, the $eq notation is used for a literal match.
If you know that "1611292138" will always be the first element of the sub-array, your query becomes simpler:
{ "stock" : { "$elemMatch" : { "0" : "1611292138" } } }
Explanation:
Scan all arrays under stock
Look for "1611292138" in the first slot of each sub-array
Use nested $elemMatch as below :
db.vlc.find({stock: { "$elemMatch":{"$elemMatch":{"$all":["1612050918"]}}}})
Or
db.vlc.find({stock: {"$elemMatch":{ "$elemMatch":{"$in" : ["1612050918"]}}}})
Model.find({ $text : {$search: "#text"} })
returns everything that includes "text", not only those documents with "#text". I've tried putting an \ before the #, to no avail. How do I stop mongodb from ignoring my special characters? Thanks.
Tomalak's description of how text indexing works is correct, but you can actually use a text index for an exact phrase match on a phrase with a special character:
> db.test.drop()
> db.test.insert({ "_id" : 0, "t" : "hey look at all this #text" })
> db.test.insert({ "_id" : 1, "t" : "text is the best" })
> db.test.ensureIndex({ "t" : "text" })
> db.test.count({ "$text" : { "$search" : "text" } })
2
> db.test.count({ "$text" : { "$search" : "#text" } })
2
> db.test.find({ "$text" : { "$search" : "\"#text\"" } })
{ "_id" : 0, "t" : "hey look at all this #text" }
Exact phrase matches are indicated by surrounding the phrase in double quotes, which need to be escaped in the shell like "\"#text\"".
Text indexes are larger than normal indexes, but if you are doing a lot of case-insensitive exact phrase matches then they can be a better option than a standard index because they will perform better. For example, on a field t with an index { "t" : 1 }, an exact match regex
> db.test.find({ "t" : /#text/ })
performs a full index scan. The analogous (but not equivalent) text query
> db.test.find({ "$text" : { "$search" : "\"#text\"" } })
will use the text index to locate documents containing the term "text", then scan all those documents to see if they contain the full phrase "#text".
Be careful because text indexes aren't case sensitive. Continuing the example above:
> db.test.insert({ "_id" : 2, "t" : "Never seen so much #TEXT" })
> db.test.find({ "t" : /#text/ })
{ "_id" : 0, "t" : "hey look at all this #text" }
> db.test.find({ "$text" : { "$search" : "\"#text\"" } })
{ "_id" : 0, "t" : "hey look at all this #text" }
{ "_id" : 2, "t" : "Never seen so much #TEXT" }
I have a collection containing data:
{
"_id" : ObjectId("51dfb7abe4b02f15ee93a7c7"),
"date_created" : "2013-7-12 13:25:5",
"referrer_id" : 13,
"role_name" : "Physician",
"status_id" : "1",
}
I am sending the query:
cmd {
"mapreduce" : "doctor" ,
"map" : "function map(){emit(this._id,this);}" ,
"reduce" : "function reduce(key,values){return values;}" ,
"verbose" : true ,
"out" : { "merge" : "map_reduce"} ,
"query" : { "$where" : "this.demographics.first_name=='makdoctest'"}
}
I am getting error as:
"errmsg" : "exception: count failed in DBDirectClient: 10071 error on invocation of $where function:\nJS Error: TypeError: this.demographics has no properties nofile_a:0"
As Sammaye says in a comment:
It means that somewhere in one of your documents demographics is null or does not exist, you need to do a null check first, but more importantly why are you dong this in a $where?
I would go even further that that, and I wouldn't even use the Map/Reduce mechanism here. It slow, can't use indexes and can not run in parallel with others.
You would be much better off using the Aggregation Framework where you can do something like:
db.doctor.aggregate( [
{ $match: { "demographics.first_name" : 'makdoctest' } },
{ $group: …
You didn't specify the final goal here, but once you do I can update the answer.