MongoDB — используйте структуру агрегации или mapreduce для сопоставления массива строк в документах (сопоставление профилей)

Я создаю приложение, которое можно сравнить с приложением для знакомств.

У меня есть несколько документов с такой структурой:

$ db.profiles.find().pretty()

[
  {
    "_id": 1,
    "firstName": "John",
    "lastName": "Smith",
    "fieldValues": [
      "favouriteColour|red",
      "food|pizza",
      "food|chinese"
    ]
  },
  {
    "_id": 2,
    "firstName": "Sarah",
    "lastName": "Jane",
    "fieldValues": [
      "favouriteColour|blue",
      "food|pizza",
      "food|mexican",
      "pets|yes"
    ]
  },
  {
    "_id": 3,
    "firstName": "Rachel",
    "lastName": "Jones",
    "fieldValues": [
      "food|pizza"
    ]
  }
]

Я пытаюсь определить профили, которые соответствуют друг другу на одном или нескольких fieldValues.

Итак, в приведенном выше примере мой идеальный результат будет выглядеть примерно так:

<some query>

result:
[
  {
    "_id": "507f1f77bcf86cd799439011",
    "dateCreated": "2013-12-01",
    "profiles": [
      {
        "_id": 1,
        "firstName": "John",
        "lastName": "Smith",
        "fieldValues": [
          "favouriteColour|red",
          "food|pizza",
          "food|chinese"
        ]
      },
      {
        "_id": 2,
        "firstName": "Sarah",
        "lastName": "Jane",
        "fieldValues": [
          "favouriteColour|blue",
          "food|pizza",
          "food|mexican",
          "pets|yes"
        ]
      },

    ]
  },
  {
    "_id": "356g1dgk5cf86cd737858595",
    "dateCreated": "2013-12-02",
    "profiles": [
      {
        "_id": 1,
        "firstName": "John",
        "lastName": "Smith",
        "fieldValues": [
          "favouriteColour|red",
          "food|pizza",
          "food|chinese"
        ]
      },
      {
        "_id": 3,
        "firstName": "Rachel",
        "lastName": "Jones",
        "fieldValues": [
          "food|pizza"
        ]
      }
    ]
  }
]

Я думал о том, чтобы сделать это либо как уменьшение карты, либо с помощью структуры агрегации.

В любом случае «результат» будет сохранен в коллекции (согласно «результатам» выше)

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

Изменить

Короче говоря, модель нельзя легко изменить.
Это не похоже на «профиль» в традиционном смысле.

То, что я в основном хочу сделать (в псевдокоде), похоже на:

foreach profile in db.profiles.find()
  foreach otherProfile in db.profiles.find("_id": {$ne: profile._id})
    if profile.fieldValues matches any otherProfie.fieldValues
      //it's a match!

Очевидно, что такая операция очень и очень медленная!

Также стоит упомянуть, что эти данные никогда не отображаются, это буквально просто строковое значение, которое используется для «сопоставления»


person Alex    schedule 30.04.2013    source источник
comment
сколько записей в вашей коллекции профилей (приблизительно)? И вам нужен полный вывод записи профиля или только то, какие пары совпадают, или какие пары совпадают и по каким атрибутам они совпадают?   -  person Asya Kamsky    schedule 08.05.2013
comment
Кстати, почему ваш проект не включает пару Сары Джейн и Рэйчел Джонс?   -  person Asya Kamsky    schedule 09.05.2013
comment
Это действительно должно включать Сару Джейн и Рэйчел Джонс из-за еды|пиццы   -  person Alex    schedule 09.05.2013
comment
Было бы неплохо ввести полный профиль (как в моем примере «идеального результата») - не нужно знать, в каких полях они совпадали - отдельный процесс обработает это позже.   -  person Alex    schedule 09.05.2013
comment
затем просто используйте цикл, как в первой части моего ответа.   -  person Asya Kamsky    schedule 09.05.2013


Ответы (2)


MapReduce будет запускать JavaScript в отдельном потоке и использовать предоставленный вами код для создания и сокращения частей вашего документа для агрегирования по определенным полям. Вы, безусловно, можете рассматривать упражнение как агрегирование по каждому «fieldValue». Фреймворк агрегации тоже может это делать, но будет намного быстрее, так как агрегация будет выполняться на сервере на C++, а не в отдельном потоке JavaScript. Но структура агрегации может вернуть больше данных, чем 16 МБ, и в этом случае вам потребуется выполнить более сложное разбиение набора данных.

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

> db.profiles.find().forEach( function(p) { 
       print("Matching profiles for "+tojson(p));
       printjson(
            db.profiles.find(
               {"fieldValues": {"$in" : p.fieldValues},  
                                "_id" : {$gt:p._id}}
            ).toArray()
       ); 
 }  );

Выход:

Matching profiles for {
    "_id" : 1,
    "firstName" : "John",
    "lastName" : "Smith",
    "fieldValues" : [
        "favouriteColour|red",
        "food|pizza",
        "food|chinese"
    ]
}
[
    {
        "_id" : 2,
        "firstName" : "Sarah",
        "lastName" : "Jane",
        "fieldValues" : [
            "favouriteColour|blue",
            "food|pizza",
            "food|mexican",
            "pets|yes"
        ]
    },
    {
        "_id" : 3,
        "firstName" : "Rachel",
        "lastName" : "Jones",
        "fieldValues" : [
            "food|pizza"
        ]
    }
]
Matching profiles for {
    "_id" : 2,
    "firstName" : "Sarah",
    "lastName" : "Jane",
    "fieldValues" : [
        "favouriteColour|blue",
        "food|pizza",
        "food|mexican",
        "pets|yes"
    ]
}
[
    {
        "_id" : 3,
        "firstName" : "Rachel",
        "lastName" : "Jones",
        "fieldValues" : [
            "food|pizza"
        ]
    }
]
Matching profiles for {
    "_id" : 3,
    "firstName" : "Rachel",
    "lastName" : "Jones",
    "fieldValues" : [
        "food|pizza"
    ]
}
[ ]

Очевидно, вы можете настроить запрос так, чтобы он не исключал уже совпадающие профили (путем изменения {$gt:p._id} на {$ne:{p._id}} и других настроек. Но я не уверен, какую дополнительную ценность вы получите от использования структуры агрегации или mapreduce, поскольку на самом деле это не агрегирование одной коллекции в одном из его полей (судя по формату вывода, который вы показываете).Если ваши требования к формату вывода гибки, конечно, возможно, вы также можете использовать один из встроенных параметров агрегации.

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

> db.profiles.aggregate({$unwind:"$fieldValues"}, 
      {$group:{_id:"$fieldValues", 
              matchedProfiles : {$push:
               {  id:"$_id", 
                  name:{$concat:["$firstName"," ", "$lastName"]}}},
                  num:{$sum:1}
               }}, 
      {$match:{num:{$gt:1}}});
{
    "result" : [
        {
            "_id" : "food|pizza",
            "matchedProfiles" : [
                {
                    "id" : 1,
                    "name" : "John Smith"
                },
                {
                    "id" : 2,
                    "name" : "Sarah Jane"
                },
                {
                    "id" : 3,
                    "name" : "Rachel Jones"
                }
            ],
            "num" : 3
        }
    ],
    "ok" : 1
}

Это в основном говорит: «Для каждой группы fieldValue ($ unwind) по fieldValue массив совпадающих профилей _id и имен, подсчитывая, сколько совпадений накапливает каждое fieldValue ($ group), а затем исключайте те, у которых есть только один профиль, соответствующий ему.

person Asya Kamsky    schedule 07.05.2013
comment
Я думаю, что совокупный почти готов... Я не совсем уверен, что он совпадает, как я и ожидал..? Я хочу получить список профилей, которые совпадают друг с другом, это выводит группу ключей | значений и профилей, которые его содержат? - person Alex; 09.05.2013
comment
да, это потому, что он агрегируется вокруг каждого значения поля. Если вам нужен попарный вывод, вам нужно использовать цикл и самостоятельно генерировать вывод, как показано в первой части моего ответа. - person Asya Kamsky; 09.05.2013
comment
Это имеет смысл... Сейчас я играю с этим, но, похоже, слишком много совпадений... например, я изменил .toArray на .toArray().length - и профили соответствия для +tojson(p ._id) (поэтому он выводит только идентификатор) - длина некоторых массивов составляет > 350... в моей тестовой коллекции всего около 200 профилей... :-s - person Alex; 09.05.2013
comment
да, это невозможно - вы уверены, что не изменили что-то еще? Когда вы выполняете db.collection.find(), вы не можете получить больше документов, чем существует в коллекции. Я предполагаю, что вы имеете в виду, что вы изменили printjson(....find().toArray()) на print(...find().toArray.length)? это эквивалентно простому выполнению xxx.find().count() - не нужно преобразовывать в массив, чтобы получить количество совпадающих результатов. - person Asya Kamsky; 09.05.2013
comment
Плохо, ты прав... Я думаю, это именно то, что мне нужно, поэтому я назначу награду. Я начну отдельный вопрос для моего следующего, эм, вопроса....! Благодарю вас! - person Alex; 09.05.2013
comment
Как я могу использовать это из драйвера? (в частности, драйвер С#) - заголовок stackoverflow.com/questions/16458758/ - person Alex; 09.05.2013
comment
Есть ли способ что-либо сделать с результатом/преобразовать его в курсор, как сейчас, он просто выводит его на консоль...? - person Alex; 10.05.2013

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

Имея это в виду, возникает вопрос: можно ли смоделировать ваше преобразование в рамках агрегации или вам нужно вернуться к более мощному mapreduce.

Если я понимаю, что вы пытаетесь сделать, я думаю, что это возможно с платформой агрегации, если вы немного измените свою схему. Дизайн схемы — одна из самых сложных вещей в Mongo, и вам нужно учитывать множество вещей, решая, как структурировать ваши данные. Несмотря на то, что я очень мало знаю о вашем заявлении, я все равно рискну и внесу предложение.

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

{
    "_id": 2,
    "firstName": "Sarah",
    "lastName": "Jane",
    "likes": {
        "colors": ["blue"],
        "foods": ["pizza", "mexican"],
        "pets": true
    }
}

То есть хранить многозначные атрибуты в массиве. Это позволит вам воспользоваться преимуществами оператора $unwind фреймворка агрегации. (См. пример в документации Mongo.) Но, в зависимости от того, что вы пытаетесь достичь, это может быть или не быть подходящим.

Сделав шаг назад, вы, возможно, сочтете нецелесообразным использовать структуру агрегации или функцию mapreduce Mongo. Их использование влияет на производительность, и их использование для основной бизнес-логики вашего приложения может быть не очень хорошей идеей. Как правило, они предназначены для нечастых или специальных запросов, просто чтобы получить представление о своих данных. Итак, вам может быть лучше начать с «настоящего» фреймворка mapreduce. Тем не менее, я слышал случаи, когда структура агрегирования использовалась в задании cron для регулярного создания основных бизнес-данных.

person Doug Paul    schedule 01.05.2013
comment
К сожалению, я не могу редактировать схему (это ее упрощенная версия для SO, чтобы проиллюстрировать, что я пытаюсь сделать) - я добавил редактирование для дальнейшего объяснения - person Alex; 01.05.2013
comment
Хорошо. Тогда мне непонятно, какого рода ответ или совет вы ищете. Вы можете делать то, что вы сказали в своем редактировании, с помощью структуры агрегации. Вам нужна помощь в сборке этой команды агрегации? Или вам нужен более глубокий анализ компромиссов между использованием mapreduce и платформой агрегации? - person Doug Paul; 02.05.2013
comment
Извините, но помогите собрать эту команду агрегации довольно точно, и мы предложили вознаграждение за вопрос, чтобы соблазнить людей помочь! :-) - person Alex; 07.05.2013
comment
Структура агрегации не похожа на Map Reduce... они работают совершенно по-разному в разных envos, сценариях и т. д. и т. д. - person Sammaye; 08.05.2013
comment
@DougPaul, вы очень ошибаетесь в отношении отношения структуры агрегации к mapreduce. - person Asya Kamsky; 08.05.2013
comment
@AsyaKamsky: О, как так? Я думал, что довольно хорошо разбираюсь в структуре агрегации. На конференциях и в беседе с разработчиком MongoDB мне объяснили, что это структурированный (и, следовательно, оптимизируемый) способ делать то, что чаще всего делается в запросах mapreduce. Таким образом, он работает совсем по-другому, но в целом служит той же цели. Это то, что я пытался сказать выше, и я до сих пор не понимаю, как это неправильно. - person Doug Paul; 15.05.2013
comment
@Sammaye, я тоже открываю тебе свой вопрос выше. - person Doug Paul; 15.05.2013
comment
Вы должны обратиться к первой части ответа Асии за чрезвычайно кратким объяснением, конечно, оно не охватывает всю тему, но достаточно, чтобы доказать мою точку зрения. - person Sammaye; 15.05.2013
comment
@DougPaul создан для более эффективной обработки вещей, которые раньше приходилось делать с помощью mapReduce != в основном просто mapreduce это совсем не mapReduce, не имеет ничего общего с этим кодом - он полностью работает на сервере, все написано на C++, где mapreduce выполняет предоставленный javascript в отдельном потоке с сервера. - person Asya Kamsky; 16.05.2013
comment
вы также можете ознакомиться с обсуждением/ответом здесь: stackoverflow.com/questions/13908438/ - person Asya Kamsky; 16.05.2013
comment
@Ася, спасибо за продолжение. Я вижу, как мои заявления могут быть неправильно поняты; возможно, я должен был говорить точнее. (Тем не менее, я думаю, что было бы грубым преувеличением сказать, что я сильно ошибаюсь. Но что бы там ни было.) - person Doug Paul; 20.07.2013
comment
@Sammaye, кажется, теперь я вижу наше непонимание. Ваше заявление о том, что структура агрегации не имеет ничего общего с Map Reduce, рассматривает только как они работают. Когда я заявил, что они в основном одинаковы, я имел в виду только то, что они делают: агрегацию. Так что ни одно из утверждений в целом не верно. В любом случае, мой ответ призван передать: если вы можете использовать один, вы, вероятно, можете использовать и другой, но предпочитаете структуру агрегации, потому что она быстрее. Еще лучше, избегайте необходимости использовать любой из них. - person Doug Paul; 20.07.2013