Объединение запросов MongoDB с массивом идентификаторов документов как предпочтение сортировки

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

Category { _id => ..., name => ..., products => array( 0 => new MongoID(PID...1), 1 => new MongoID(PID...2), 2 => new MongoID(PID...3), .... ) } 

И коллекция продуктов:

 Product { _id => ..., name => ..., active => true, status => 'published', categories => array( 0 => new MongoID(CATID...1), 1 => new MongoID(CATID...2), 2 => new MongoID(CATID...3) ) } 

Я рефакторинг моего кода, потому что в данный момент, потому что, когда я делаю какие-либо ограничения / компенсации, я должен вернуть все продукты в категории как объекты, а затем отключить их, если они не активны (есть еще несколько совпадений контента, но я оставляя их для краткости). Очевидно, что на этом этапе мы не оптимизированы.

Я построил условный запрос, который выглядит так:

 array( '$and' => array( array( '$or' => array( 0 => array( '_id' => new MongoID(PID...1) ), 1 => array( '_id' => new MongoID(PID...2) ), 2 => array( '_id' => new MongoID(PID...3) ), ... ) ), array( '$and' => array( array( 'active' => true ), array( 'status' => 'published' ) ) ) ) ) 

То, что мне нужно сделать, это уважать порядок _id в первой части запроса и смещение / ограничение, на основе которого документы соответствуют второй части в последовательности _id.

Запрос в конечном итоге заканчивается командой find (), но установка порядка сортировки, очевидно, не будет работать, потому что мы не сортируем.

Если вы не разделяете некоторые поля из Продукта и добавляете в Категория-> продукты [], я изо всех сил пытаюсь найти способ сделать это. Это просто случай обращения с MapReduce / aggregation сейчас или есть более простая альтернатива?

Благодаря!

Если вам нужно поддерживать «порядок» таким образом, тогда ваш лучший вариант – присвоить «вес» выбранным вами элементам. Это дает вам кое-что для сортировки.

Вы можете сделать это с помощью .aggregate() , и вы можете, возможно, сделать то же самое с mapReuce, но «совокупный» способ должен работать быстрее. Кроме того, синтаксис здесь кажется немного неактивным. Вместо того, чтобы использовать $or над тем же полем, вы, вероятно, хотите $in .

В общем виде JavaScript / JSON:

 var idArray = [ 5, 2, 8 ]; db.collection.aggregate([ // Match the selected documents by "_id" { "$match": { "_id": { "$in": [ 5, 2, 8 ] }, "active": true, "status": "published" }, // Project a "weight" to each document { "$project": { "name": 1, "active": 1, "status": 1, "weight": { "$cond": [ { "$eq": [ "_id", 5 ] }, 1, { "$cond": [ { "$eq": [ "_id", 2 ] }, 2, 3 ]} ]} }}, // Sort the results { "$sort": { "weight": 1 } } ]) 

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

Вложенное использование $cond оценивало логическое условие, соответствующее «значению» поля _id и присваивает «вес» как увеличивающееся число. Это поддерживает порядок значений в вашем входном массиве.

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

Но это позволит вам поддерживать порядок вашего входного массива


PHP-код

Например, что-то вроде этого для создания требуемого конвейера:

 $list = array( 5, 2, 8 ); $stack = array(); for( $i = count($list)-1; $i > 0; $i-- ) { $rec = array( '$cond' => array( array( '$eq' => array( '$_id', $list[$i-1] ) ), $i ) ); if ( count($stack) == 0 ) { $rec['$cond'][] = $i+1; } else { $last = array_pop($stack); $rec['$cond'][] = $last; } $stack[] = $rec; } $pipeline = array( array( '$match' => array( '_id' => array( '$in' => $list ), 'active' => true, 'status' => "published" ) ), array( '$project' => array( 'name' => 1, 'active' => 1, 'status' => 1, 'weight' => $stack[0] ) ), array( '$sort' => array( 'weight' => 1 ) ) ); echo json_encode( $pipeline, JSON_PRETTY_PRINT ) . "\n"; 

Конечно, вы не «кодируете» JSON на самом деле, это просто показать структуру, которая сформирована.


Уменьшение карты

И просто чтобы показать, что вы можете делать то же самое с mapReduce . Сначала установите устройство отображения

 var mapper = function () { var order = inputs.indexOf(this._id); emit( order, { doc: this } ); } 

И, может быть, функция финализации для очистки вещей «немного»:

 var finalize = function (key, value) { return value.doc; } 

mapReduce . Редуктор не требуется:

 db.test.mapReduce( mapper, function(){}, { "out": { "inline": 1 }, "query": { "_id": { "$in": idArray } }, "scope": { "inputs": idArray } , "finalize": finalize } ) 

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