Поиск полного и частичного текста MongoDB

Env:

  • MongoDB (3.2.0) с MongoS

Коллекция:

  • пользователей

Создание текстового индекса:

  BasicDBObject keys = new BasicDBObject();
  keys.put("name","text");

  BasicDBObject options = new BasicDBObject();
  options.put("name", "userTextSearch");
  options.put("unique", Boolean.FALSE);
  options.put("background", Boolean.TRUE);

  userCollection.createIndex(keys, options); // using MongoTemplate

Документ

  • { "имя": "Леонель" }

Запросы

  • db.users.find( { "$text" : { "$search" : "LEONEL" } } ) = > НАЙДЕНО
  • db.users.find( { "$text" : { "$search" : "LEONEL" } } ) = > НАЙДЕНО (регистр поиска нечувствителен)
  • db.users.find( { "$text" : { "$search" : "LEONÉL" } } ) = > НАЙДЕН (поиск с диакритической чувствительностью ложный)
  • db.users.find( { "$text" : { "$search" : "LEONE" } } ) = > НАЙДЕН (Частичный поиск)
  • db.users.find( { "$text" : { "$search" : "LEO" } } ) = > НЕ НАЙДЕН (Частичный поиск)
  • db.users.find( { "$text" : { "$search" : "L" } } ) = > НЕ НАЙДЕН (Частичный поиск)

Любая идея, почему я получаю 0 результатов, используя в качестве запроса "LEO" или "L"?

Regex с индексом индекса не допускается.

db.getCollection('users')
     .find( { "$text" : { "$search" : "/LEO/i", 
                          "$caseSensitive": false, 
                          "$diacriticSensitive": false }} )
     .count() // 0 results

db.getCollection('users')
     .find( { "$text" : { "$search" : "LEO", 
                          "$caseSensitive": false, 
                          "$diacriticSensitive": false }} )
.count() // 0 results

Монго Документация:

Ответ 1

Как и в MongoDB 3.4, функция текстового поиска разработана для поддержки нечувствительного к регистру поиска текстового содержимого с помощью языковых правил для стоп-слов и основ. Правила происхождения для поддерживаемых языков основаны на стандартных алгоритмах, которые обычно обрабатывают общие глаголы и существительные, но не знают собственных имен.

Не существует явной поддержки частичных или нечетких совпадений, но термины, основанные на сходном результате, могут работать как таковые. Например: "вкус", "вкус" и "вкус" - все это связано с "вкусом". Попробуйте демонстрационную страницу "Steamming" в Snowball, чтобы поэкспериментировать с другими словами и алгоритмами.

Ваши результаты, которые соответствуют, являются вариациями одного и того же слова "LEONEL" и отличаются только регистром и диакритическим знаком. Если "LEONEL" не может быть ограничен чем-то более коротким по правилам выбранного вами языка, это единственный тип вариантов, который будет соответствовать.

Если вы хотите делать эффективные частичные совпадения, вам нужно использовать другой подход. Для некоторых полезных идей см.:

Есть соответствующий запрос на улучшение, который вы можете просмотреть/повысить в системе отслеживания проблем MongoDB: SERVER-15090: Улучшить текстовые индексы для поддержки частичного совпадения слов.

Ответ 2

Так как Mongo в настоящее время не поддерживает частичный поиск по умолчанию...

Я создал простой статический метод.

import mongoose from 'mongoose'

const PostSchema = new mongoose.Schema({
    title: { type: String, default: '', trim: true },
    body: { type: String, default: '', trim: true },
});

PostSchema.index({ title: "text", body: "text",},
    { weights: { title: 5, body: 3, } })

PostSchema.statics = {
    searchPartial: function(q, callback) {
        return this.find({
            $or: [
                { "title": new RegExp(q, "gi") },
                { "body": new RegExp(q, "gi") },
            ]
        }, callback);
    },

    searchFull: function (q, callback) {
        return this.find({
            $text: { $search: q, $caseSensitive: false }
        }, callback)
    },

    search: function(q, callback) {
        this.searchFull(q, (err, data) => {
            if (err) return callback(err, data);
            if (!err && data.length) return callback(err, data);
            if (!err && data.length === 0) return this.searchPartial(q, callback);
        });
    },
}

export default mongoose.models.Post || mongoose.model('Post', PostSchema)

Как пользоваться:

import Post from '../models/post'

Post.search('Firs', function(err, data) {
   console.log(data);
})

Ответ 3

Я завернул ответ @Ricardo Canelas в плагин для мангустов здесь на npm

Два изменения сделаны: - Использует обещания - Поиск по любому полю с типом String

Вот важный исходный код:

// mongoose-partial-full-search

module.exports = exports = function addPartialFullSearch(schema, options) {
  schema.statics = {
    ...schema.statics,
    makePartialSearchQueries: function (q) {
      if (!q) return {};
      const $or = Object.entries(this.schema.paths).reduce((queries, [path, val]) => {
        val.instance == "String" &&
          queries.push({
            [path]: new RegExp(q, "gi")
          });
        return queries;
      }, []);
      return { $or }
    },
    searchPartial: function (q, opts) {
      return this.find(this.makePartialSearchQueries(q), opts);
    },

    searchFull: function (q, opts) {
      return this.find({
        $text: {
          $search: q
        }
      }, opts);
    },

    search: function (q, opts) {
      return this.searchFull(q, opts).then(data => {
        return data.length ? data : this.searchPartial(q, opts);
      });
    }
  }
}

exports.version = require('../package').version;

Usage

Usage
// PostSchema.js
import addPartialFullSearch from 'mongoose-partial-full-search';
PostSchema.plugin(addPartialFullSearch);

// some other file.js
import Post from '../wherever/models/post'

Post.search('Firs').then(data => console.log(data);)

Ответ 4

Без создания индекса мы могли бы просто использовать:

db.users.find({ name: /<full_or_partial_text>/i}) (без учета регистра)

Ответ 5

import re

db.collection.find({"$or": [{"your field name": re.compile(text, re.IGNORECASE)},{"your field name": re.compile(text, re.IGNORECASE)}]})