Получить документы с тегами в списке, упорядоченные по общему количеству совпадений - PullRequest
8 голосов
/ 23 декабря 2011

Учитывая следующую коллекцию документов MongoDB:

{
 title : 'shirt one'
 tags : [
  'shirt',
  'cotton',
  't-shirt',
  'black'
 ]
},
{
 title : 'shirt two'
 tags : [
  'shirt',
  'white',
  'button down collar'
 ]
},
{
 title : 'shirt three'
 tags : [
  'shirt',
  'cotton',
  'red'
 ]
},
...

Как получить список элементов, соответствующих списку тегов, упорядоченных по общему количеству совпадающих тегов? Например, учитывая этот список тегов в качестве входных данных:

['shirt', 'cotton', 'black']

Я хочу получить элементы, упорядоченные в порядке убывания по общему количеству совпадающих тегов:

item          total matches
--------      --------------
Shirt One     3 (matched shirt + cotton + black)
Shirt Three   2 (matched shirt + cotton)
Shirt Two     1 (matched shirt)

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

Но в Монго ...?

Кажется, этот подход может сработать,

  • разбить входные теги на несколько операторов IN
  • запросить элементы путем "ИЛИ" вместе с тегами
    • т.е. где («рубашка» IN items.tags) ИЛИ («рубашка» IN items.tags)
    • это вернет, например, три экземпляра "Shirt One", 2 экземпляра "Shirt Three" и т. Д.
  • отобразить / уменьшить этот вывод
    • map: emit (this._id, {...});
    • уменьшить: подсчитать общее количество вхождений _id
    • Завершение: сортировка по подсчитанному итогу

Но мне не ясно, как реализовать это как запрос Монго, или если это даже самый эффективный подход.

Ответы [ 3 ]

8 голосов
/ 13 сентября 2012

Как я ответил в В MongoDB поиск в массиве и сортировка по количеству совпадений

Это возможно с использованием Aggregation Framework.

Предположения

  • tags атрибут является набором (без повторяющихся элементов)

Запрос

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

db.test_col.aggregate(
    {$match: {tags: {$in: ["shirt","cotton","black"]}}}, 
    {$unwind: "$tags"}, 
    {$match: {tags: {$in: ["shirt","cotton","black"]}}},
    {$group: {
        _id:{"_id":1}, 
        matches:{$sum:1}
    }}, 
    {$sort:{matches:-1}}
);

Ожидаемые результаты

{
    "result" : [
        {
            "_id" : {
                "_id" : ObjectId("5051f1786a64bd2c54918b26")
            },
            "matches" : 3
        },
        {
            "_id" : {
                "_id" : ObjectId("5051f1726a64bd2c54918b24")
            },
            "matches" : 2
        },
        {
            "_id" : {
                "_id" : ObjectId("5051f1756a64bd2c54918b25")
            },
            "matches" : 1
        }
    ],
    "ok" : 1
}
5 голосов
/ 23 декабря 2011

Сейчас это невозможно сделать, если вы не используете MapReduce. Единственная проблема с MapReduce в том, что он медленный (по сравнению с обычным запросом).

Структура агрегирования рассчитана на 2.2 (поэтому она должна быть доступна в версии 2.1 dev) и должна значительно облегчить эту задачу без MapReduce.

Лично я не думаю, что использование M / R - эффективный способ сделать это. Я бы предпочел запросить все документы и выполнить эти расчеты на стороне приложения. Масштабировать серверы приложений проще и дешевле, чем масштабировать серверы баз данных, поэтому пусть серверы приложений занимаются подсчетом чисел. Из них этот подход может не работать для вас, учитывая ваши шаблоны доступа к данным и требования.

Еще более простой подход может заключаться в том, чтобы просто включить свойство count в каждый из ваших объектов тегов, и всякий раз, когда вы $push добавляете новый тег в массив, вы также $inc свойстве count. Это распространенная модель в мире MongoDB, по крайней мере до структуры агрегации.

1 голос
/ 23 декабря 2011

Я буду вторым @Bryan, говоря, что MapReduce - единственный возможный способ в настоящее время (и это далеко не идеально).Но, если вам это отчаянно нужно, вот, пожалуйста: -)

    var m = function() {
        var searchTerms = ['shirt', 'cotton', 'black'];
        var me = this;
        this.tags.forEach(function(t) {
            searchTerms.forEach(function(st) {
                if(t == st) {
                    emit(me._id, {matches : 1});
                }
            })
        })
    };

    var r = function(k, vals) {
        var result = {matches : 0};
        vals.forEach(function(v) {
            result.matches += v.matches;
        })
        return result;
    };

    db.shirts.mapReduce(m, r, {out: 'found01'});

    db.found01.find();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...