Фасетный поиск с использованием MongoDB

Я собираюсь использовать MongoDB для своего следующего проекта. Одно из основных требований к этому приложению - обеспечение фасетного поиска. Кто-нибудь пробовал использовать MongoDB для выполнения фасетного поиска?

У меня есть модель продукта с различными атрибутами, такими как размер, цвет, бренд и т. Д. При поиске продукта это приложение Rails должно отображать фасетные фильтры на боковой панели. Фасетные фильтры будут выглядеть примерно так:

Size:
XXS (34)
XS (22)
S (23)
M (37)
L (19)
XL (29)

Color:
Black (32)
Blue (87)
Green (14)
Red (21)
White (43)

Brand:
Brand 1 (43)
Brand 2 (27)

person Firoz Ansari    schedule 14.09.2012    source источник


Ответы (5)


Я думаю, что с помощью Apache Solr или ElasticSearch вы получите больше гибкости и производительности, но это поддерживается с помощью Aggregation Framework.

Основная проблема при использовании MongoDB заключается в том, что вы должны запрашивать его N раз: сначала для получения совпадающих результатов, а затем один раз для каждой группы; при использовании системы полнотекстового поиска вы получаете все это одним запросом.

Пример

//'tags' filter simulates the search
//this query gets the products
db.products.find({tags: {$all: ["tag1", "tag2"]}})

//this query gets the size facet
db.products.aggregate(
    {$match: {tags: {$all: ["tag1", "tag2"]}}}, 
    {$group: {_id: "$size"}, count: {$sum:1}}, 
    {$sort: {count:-1}}
)

//this query gets the color facet
db.products.aggregate(
    {$match: {tags: {$all: ["tag1", "tag2"]}}}, 
    {$group: {_id: "$color"}, count: {$sum:1}}, 
    {$sort: {count:-1}}
)

//this query gets the brand facet
db.products.aggregate(
    {$match: {tags: {$all: ["tag1", "tag2"]}}}, 
    {$group: {_id: "$brand"}, count: {$sum:1}}, 
    {$sort: {count:-1}}
)

После того как пользователь отфильтрует поиск с использованием фасетов, вы должны добавить этот фильтр для запроса предиката и предиката соответствия следующим образом.

//user clicks on "Brand 1" facet
db.products.find({tags: {$all: ["tag1", "tag2"]}, brand: "Brand 1"})

db.products.aggregate(
    {$match: {tags: {$all: ["tag1", "tag2"]}}, brand: "Brand 1"}, 
    {$group: {_id: "$size"}, count: {$sum:1}}, 
    {$sort: {count:-1}}
)

db.products.aggregate(
    {$match: {tags: {$all: ["tag1", "tag2"]}}, brand: "Brand 1"}, 
    {$group: {_id: "$color"}, count: {$sum:1}}, 
    {$sort: {count:-1}}
)

db.products.aggregate(
    {$match: {tags: {$all: ["tag1", "tag2"]}}, brand: "Brand 1"}, 
    {$group: {_id: "$brand"}, count: {$sum:1}}, 
    {$sort: {count:-1}}
)
person Samuel García    schedule 15.09.2012
comment
Фреймворк агрегации кажется многообещающим. Я не вижу проблем с выполнением дополнительных запросов для каждой фасетной группы. Позвольте мне создать приложение POC для проверки этой реализации. - person Firoz Ansari; 17.09.2012
comment
Да, это действительно мощно и дает нам много возможностей. Основная проблема с этим фреймворком - оптимизация запросов. При использовании сегментирования отсутствует оптимизация запросов. Я работаю над исправлением этой проблемы и вытаскиваю ее в github. - person Samuel García; 18.09.2012

Mongodb 3.4 представляет фасетный поиск.

Этап $ facet позволяет создавать многогранные агрегаты, которые характеризуют данные по нескольким измерениям или фасетам в рамках одного этапа агрегирования. Многогранные агрегаты предоставляют несколько фильтров и категорий для управления просмотром и анализом данных.

Входные документы передаются на этап $ facet только один раз.

Теперь вам не нужно запрашивать N раз для получения агрегатов по N группам.

$ facet позволяет выполнять различные агрегаты для одного и того же набора входных документов без необходимости извлекать входные документы несколько раз.

Пример запроса для варианта использования OP будет примерно таким:

db.products.aggregate( [
  {
    $facet: {
      "categorizedByColor": [
        { $match: { color: { $exists: 1 } } },
        {
          $bucket: {
            groupBy: "$color",
            default: "Other",
            output: {
              "count": { $sum: 1 }
            }
          }
        }
      ],
      "categorizedBySize": [
        { $match: { size: { $exists: 1 } } },
        {
          $bucket: {
            groupBy: "$size",
            default: "Other",
            output: {
              "count": { $sum: 1 }
            }
          }
        }
      ],
      "categorizedByBrand": [
        { $match: { brand: { $exists: 1 } } },
        {
          $bucket: {
            groupBy: "$brand",
            default: "Other",
            output: {
              "count": { $sum: 1 }
            }
          }
        }
      ]
    }
  }
])
person Rahul    schedule 24.12.2016
comment
вам все равно нужно будет выполнить два поиска, хотя один правильный для документов, а затем приведенный здесь пример для связанных аспектов? - person Ominus; 03.08.2017
comment
Да ... похоже на это. Он просто решает вариант использования для нескольких аспектов в одном запросе. - person Rahul; 03.08.2017
comment
Есть ли способ в mongodb для запуска только одного запроса для получения документов и связанных аспектов? - person qmn1711; 04.07.2018

Популярным вариантом расширенного поиска с помощью MongoDB является использование ElasticSearch совместно с поддерживаемым сообществом Плагин MongoDB River. Плагин MongoDB River передает поток документов из MongoDB в ElasticSearch для индексации.

ElasticSearch - это распределенная поисковая система, основанная на Apache Lucene и имеющая интерфейс RESTful JSON через http. Существует Facet Search API и ряд других дополнительных функций, таких как как Percolate и " Еще как это ".

person Stennie    schedule 14.09.2012

Вы можете сделать запрос, вопрос будет в том, быстро это или нет. т.е. что-то вроде:

find( { size:'S', color:'Blue', Brand:{$in:[...]} } )

тогда вопрос в том, как обстоят дела с производительностью. Специального средства для фасетного поиска в продукте пока нет. В будущем могут быть некоторые установленные планы запросов, подобные пересечению, которые хороши, но это tbd / future.

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

  • Вы можете использовать составные индексы, которые, возможно, объединяют два или более свойств. Если у вас небольшое количество свойств, это может сработать. Индекс не обязательно должен использовать все запросы переменных, но в указанном выше составной индекс для любых двух из трех, вероятно, будет работать лучше, чем индекс для одного элемента.

  • Если у вас не слишком много скусов, подойдет грубая сила; например если у вас скучает 1 мм, сканирование таблицы в оперативной памяти может быть достаточно быстрым. в этом случае я бы сделал таблицу только со значениями фасетов, сделал бы ее как можно меньше и сохранил бы полные документы SKU в отдельной коллекции. например.:

    facets_collection: {sz: 1, brand: 123, clr: 'b', _ id:} ...

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

если вы создаете несколько индексов, вероятно, лучше не создавать их так много, чтобы они больше не помещались в оперативной памяти.

учитывая, что запрос выполняется, и это вопрос производительности, который можно было бы просто использовать с mongo, и если он недостаточно быстр, то подключите solr.

person user3973    schedule 15.09.2012

Фасетное решение (основанное на подсчете) зависит от дизайна вашего приложения.

db.product.insert(
{
 tags :[ 'color:green','size:M']

}
)

Однако, если можно передать данные в указанном выше формате, в котором фасеты и их значения объединены вместе, чтобы сформировать согласованный тег, тогда используйте запрос ниже

db.productcolon.aggregate(
   [
      { $unwind : "$tags" },
      {
        $group : {
          _id : '$tags',
          count: { $sum: 1 }
        }
      }
   ]
)

Смотрите результат ниже

{ 
    "_id" : "color:green", 
    "count" : NumberInt(1)
}
{ 
    "_id" : "color:red", 
    "count" : NumberInt(1)
}
{ 
    "_id" : "size:M", 
    "count" : NumberInt(3)
}
{ 
    "_id" : "color:yellow", 
    "count" : NumberInt(1)
}
{ 
    "_id" : "height:5", 
    "count" : NumberInt(1)
}

После этого шага сервер приложений может выполнить группировку по цвету / размеру перед отправкой обратно клиенту.

Примечание. Подход к объединению фасета и его значений дает вам агрегированные значения всех фасетов, и вы можете избежать: «Основная проблема с использованием MongoDB заключается в том, что вы должны запрашивать его N раз: сначала для получения результатов соответствия, а затем один раз для каждой группы; при использовании система полнотекстового поиска - вы получите все в одном запросе ». см. ответ Гарсии

person Faiz Mohamed Haneef    schedule 16.02.2016