Я хотел бы отфильтровать список и отсортировать его на основе совокупности; что-то, что довольно просто выразить в SQL, но я озадачен тем, как лучше всего сделать это с помощью итеративного сокращения карты. Я специально использую дополнение Cloudant «dbcopy» к CouchDB, но я думаю, что этот подход может быть аналогичен другим архитектурам map/reduce.
Псевдокод SQL может выглядеть так:
SELECT grouping_field, aggregate(*)
FROM data
WHERE #{filter}
GROUP BY grouping_field
ORDER BY aggregate(*), grouping_field
LIMIT page_size
Фильтр может искать совпадение или искать в диапазоне; например field in ('foo', 'bar')
или field between 37 and 42
.
В качестве конкретного примера рассмотрим набор данных электронных писем; поле группировки может быть «Идентификатор списка», «Отправитель» или «Тема»; агрегатная функция может быть count(*)
, max(date)
или min(date)
; а предложение фильтра может учитывать флаги, диапазон дат или идентификатор почтового ящика. Документы могут выглядеть так:
{
"id": "foobar", "mailbox": "INBOX", "date": "2013-03-29",
"sender": "[email protected]", "subject": "Foo Bar"
}
Получить количество писем от одного и того же отправителя тривиально:
"map": "function (doc) { emit(doc.sender, null) }",
"reduce": "_count"
Если я добавлю фильтр к ключам представления (например, окончательный результат выглядит как {"key": ["INBOX", 1234, "[email protected]"], "value": null}
, то сортировка по количеству в пределах одного значения фильтра тривиальна. Но сортировка этих данных по количеству с несколькими фильтрами потребует обхода весь набор данных (по ключу), что слишком медленно для больших наборов данных.
Или я мог бы создать индекс для каждого потенциального выбора фильтра; например окончательный результат выглядит как {"key": [["mbox1", "mbox2"], 1234, "[email protected]"], "value": null},
(если выбраны оба "mbox1" и "mbox2") или {"key": [["mbox1"], 1234, "[email protected]"], "value": {...}},
(если выбран только "mbox1"). Это легко запросить и быстро. Но похоже, что размер индекса на диске будет расти экспоненциально (с количеством отдельных отфильтрованных полей). И это кажется совершенно неприемлемым для фильтрации открытых данных, таких как диапазоны дат.
Наконец, я мог бы динамически генерировать представления, которые обрабатывают нужные фильтры на лету, только по мере необходимости, и удалять их после того, как они больше не используются (для экономии места на диске). Недостатками здесь являются гигантский скачок сложности кода и большие первоначальные затраты каждый раз, когда выбирается новый фильтр.
Есть ли способ лучше?