Бих искал да филтрирам списък и да го сортирам въз основа на агрегат; нещо, което е доста лесно за изразяване в SQL, но съм озадачен относно най-добрия начин да го направя с итеративно Map Reduce. Специално използвам добавката "dbcopy" на Cloudant към CouchDB, но мисля, че подходът може да е подобен с други архитектури за карта/намаляване.
Псевдокодът 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
.
Като конкретен пример, помислете за набор от имейли; полето за групиране може да бъде "List-id", "Sender" или "Subject"; агрегатната функция може да бъде 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"
И Cloudant има добър пример за сортиране по брой при второто преминаване на намаляване на картата. Но когато искам да филтрирам (например по пощенска кутия), нещата бързо се объркват.
Ако добавя филтъра към ключовете за преглед (напр. крайният резултат изглежда като {"key": ["INBOX", 1234, "[email protected]"], "value": null}
, тогава е тривиално да сортирате по брой в рамките на една стойност на филтъра. Но сортирането на тези данни по брой с множество филтри ще изисква преминаване през целия набор от данни (на ключ), което е твърде бавно при големи набори от данни.
Или мога да създам индекс за всеки потенциален избор на филтър; напр. крайният резултат изглежда като {"key": [["mbox1", "mbox2"], 1234, "[email protected]"], "value": null},
(когато са избрани и „mbox1“ и „mbox2“) или {"key": [["mbox1"], 1234, "[email protected]"], "value": {...}},
(когато е избрано само „mbox1“). Това е лесно за запитване и бързо. Но изглежда, че размерът на диска на индекса ще нараства експоненциално (с броя на отделните филтрирани полета). И изглежда, че е напълно несъстоятелно за филтриране на отворени данни, като диапазони от дати.
И накрая, мога динамично да генерирам изгледи, които обработват желаните филтри в движение, само при необходимост, и да ги разруша, след като вече не се използват (за да спестя дисково пространство). Недостатъците тук са огромен скок в сложността на кода и големи предварителни разходи всеки път, когато се избира нов филтър.
Има ли по-добър начин?