55

I have mongodb with a $text-Index and elements like this:

{
   foo: "my super cool item"
}
{
   foo: "your not so cool item"
}

If i do search with

mycoll.find({ $text: { $search: "super"} })

i get the first item (correct).

But i also want to search with "uper" to get the fist item - but if i try:

mycoll.find({ $text: { $search: "uper"} })

I dont get any results.

My Question: If there is a way to use $text so its finds results with a part of the searching string? (e.g. like '%uper%' in mysql)

Attention: I dont ask for a regex only search - i ask for a regex-search within a $text-search!

mdunisch
  • 3,627
  • 5
  • 25
  • 41

7 Answers7

51

It's not possible to do it with $text operator.

Text indexes are created with the terms included in the string value or in an array of strings and the search is based in those indices.

You can only group terms on a phrase but not take part of them.

Read $text operator reference and text indexes description.

Christopher Moore
  • 15,626
  • 10
  • 42
  • 52
francadaval
  • 2,451
  • 3
  • 26
  • 36
18

The best solution is to use both a text index and a regex.
The index will provide excellent speed performances but won't match as many documents as a regex.
The regex will allow a fallback in case the index doesn't return enough results.

db.mycoll.createIndex({ foo: 'text' });
db.mycoll.createIndex({ foo: 1 });
db.mycoll.find({
  $or: [
    { $text: { $search: 'uper' } },
    { foo: { $regex: 'uper' } }
  ]
});

For even better performances (but slightly different results), use ^ inside the regex:

db.mycoll.find({
  $or: [
    { $text: { $search: 'uper' } },
    { foo: { $regex: '^uper' } }
  ]
});
Jean-Baptiste Martin
  • 1,399
  • 1
  • 10
  • 19
15

What you are trying to do in your second example is prefix wildcard search in your collection mycoll on field foo. This is not something the textsearch feature is designed for and it is not possible to do it with $text operator. This behaviour does not include wildcard prefix search on any given token in the indexed field. However you can alternatively perform regex search as others suggested. Here is my walkthrough:

>db.mycoll.find()
{ "_id" : ObjectId("53add9364dfbffa0471c6e8e"), "foo" : "my super cool item" }
{ "_id" : ObjectId("53add9674dfbffa0471c6e8f"), "foo" : "your not so cool item" }
> db.mycoll.find({ $text: { $search: "super"} })
{ "_id" : ObjectId("53add9364dfbffa0471c6e8e"), "foo" : "my super cool item" }
> db.mycoll.count({ $text: { $search: "uper"} })
0

The $text operator supports search for a single word, search for one or more words or search for phrase. The kind of search you wish is not supported

The regex solution:

> db.mycoll.find({foo:/uper/})
{ "_id" : ObjectId("53add9364dfbffa0471c6e8e"), "foo" : "my super cool item" }
> 

The answer to your final question: to do mysql style %super% in mongoDB you would most likely have to do:

db.mycoll.find( { foo : /.*super.*/ } );
  • 2
    Thank you for your detailed answer. I want to use the $text-feature because of "Text Score" and Language-Fields. But i understand form @rancadaval answer that $text only work with strings. – mdunisch Jun 28 '14 at 12:49
  • 1
    @user3351722 yes, with `$text` you can search for a word, a list of words or phrases. I updated the answer as well. –  Jun 28 '14 at 17:30
  • 1
    So how do you deal with search on multiple fields? – HaBo May 30 '16 at 06:54
12

It should work with /uper/.

See http://docs.mongodb.org/manual/reference/operator/query/regex/ for details.

Edit:

As per request in the comments:

The solution wasn't necessarily meant to actually give what the OP requested, but what he needed to solve the problem.

Since $regex searches don't work with text indices, a simple regex search over an indexed field should give the expected result, though not using the requested means.

Actually, it is pretty easy to do this:

db.collection.insert( {foo: "my super cool item"} )
db.collection.insert( {foo: "your not so cool item"})
db.collection.ensureIndex({ foo: 1 })
db.collection.find({'foo': /uper/})

gives us the expected result:

{ "_id" : ObjectId("557f3ba4c1664dadf9fcfe47"), "foo" : "my super cool item" }

An added explain shows us that the index was used efficiently:

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.collection",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "foo" : /uper/
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "inputStage" : {
                "stage" : "IXSCAN",
                "filter" : {
                    "foo" : /uper/
                },
                "keyPattern" : {
                    "foo" : 1
                },
                "indexName" : "foo_1",
                "isMultiKey" : false,
                "direction" : "forward",
                "indexBounds" : {
                    "foo" : [
                        "[\"\", {})",
                        "[/uper/, /uper/]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "serverInfo" : {
        // skipped
    },
    "ok" : 1
}

To make a long story short: No, you can not reuse a $text index, but you can do the query efficiently. Like written in Implement auto-complete feature using MongoDB search , one could probably be even more efficient by using a map/reduce approach, eliminating redundancy and unnecessary stop words from the indices, at the cost of being not real time any more.

Community
  • 1
  • 1
Markus W Mahlberg
  • 19,711
  • 6
  • 65
  • 89
  • That's the `$regex` operator, which is not what OP asked about. It seems the `$text` operator does not support wildcards. – zjm555 Feb 19 '15 at 19:07
  • 3
    @zjm555: Well, it might be a solution to OPs problem in the first place. That's what is called "creative problem solving" and might be worth a try. ;) – Markus W Mahlberg Feb 22 '15 at 19:49
  • Would like to know the reason for down voting. Did I miss something? – Markus W Mahlberg Apr 30 '15 at 05:43
  • $search operator doesnt support regex. It only supports string which doesn't do a substring search on indexed fields. That's why it doesn't work. I tried it so you might as well should once to verify your answer – deepak Jun 05 '15 at 21:40
  • Can you please include an example in your answer? – Michael Cole Jun 15 '15 at 20:20
  • 2
    This query doesn't use index efficiently, whole collection has to be scanned in worst scenario. Use `explain('executionStats')` and find out yourself... or just look at the index bounds in your answer. Such index is efficient only if you query for a prefix. – Sebastian Nowak Feb 25 '16 at 01:10
  • 4
    Also: this can only work "properly" with single field indexes. If you have index on `{foo: "text", bar: "text"}` and want to search in both of them you have to use `.find({$text: {$search: query}})` and then your answer won't work. – Enethion May 04 '16 at 11:17
3

As francadaval said, text index is searching by terms but if you combine regex and text-index you should be good.

mycoll.find({$or: [ 
  { 
    $text: {
      $search: "super"
    }
  },
  {
    'column-name': {
      $regex: 'uper',
      $options: 'i'
  }
]})

Also, make sure that you have normal index applied to the column other than text index.

Victor Schröder
  • 6,738
  • 2
  • 42
  • 45
jasenkoh
  • 4,011
  • 2
  • 19
  • 24
1

if you go with regex you can achieve search for "super cool" but not "super item", to achieve both request do an or request with $text and $regex for the search term.

make sure you index both text indexing and normal indexing to work.

0

You could have achieved is as-

db.mycoll.find( {foo: { $regex :  /uper/i  } })

Here 'i' is an option, denotes case-insensitive search

mohit_IBS
  • 161
  • 5