Эффективная реализация фасетного поиска в реляционных базах данных

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

Я могу получить все элементы, присвоив категории, используя внутренние соединения и получить количество элементов во всех категориях, используя COUNT и GROUP BY, но я не уверен как он будет масштабироваться до миллионов объектов и тысяч тегов. Особенно подсчет.

Я знаю, что есть некоторые нереляционные решения, такие как Lucene + SOLR, но я нашел также некоторые реализации на основе СУБД с закрытым исходным кодом, которые, как говорят, являются Entreprise-strength как FacetMap.com или Endeca программное обеспечение, поэтому должен быть эффективный способ выполнения фасетного поиска в реляционных базах данных.

есть ли у кого-нибудь опыт в фасеточном поиске и может дать несколько советов?

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

Edit:

пример фасетной навигации можно найти здесь: фламенко.

В настоящее время у меня есть стандартная схема 3-таблицы (элементы, теги и items_tags, как описано здесь: http://www.pui.ch/phred/archives/2005/04/tags-database-schemas.html#toxi) плюс таблица для фасетов. Каждому тегу присвоен фасет.

4 ответов


Я могу только подтвердить, что говорит Нильс. СУБД не подходят для многомерного поиска. Я работал с некоторыми умными решениями, счетчиками кэширования, использованием триггеров и т. д. Но, в конце концов, внешний выделенный индексатор всегда выигрывает.

возможно, если вы преобразуете свои данные в размерную модель и передаете ее в какой - то OLAP [я имею в виду MDX engine] - он будет работать хорошо. Но это кажется слишком тяжелым решением, и это будет определенно не в режиме реального времени.

наоборот, решение с выделенным двигателем индексирования (подумайте Lucene, подумайте Сфинкс) можно сделать почти в реальном времени с инкрементными обновлениями индекса.


IMO, реляционные базы данных не так хороши в поиске. Вы получите лучшую производительность от выделенной поисковой системы (например, Solr/Lucene).


граненый Поиск является аналитической проблемой, что означает, что размерный дизайн является хорошей ставкой. То, что вы ищете, должно быть в табличной форме.

включить все интересующие столбцы в аналитическую таблицу.

поставить непрерывные значения в ведра.

используйте логические столбцы для" многих "элементов, таких как категории или теги, например, если есть три тега" foo"," bar "и" baz", у вас будет три логических столбца.

использовать материализованное представление чтобы создать аналитическую таблицу.

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

фильтровать только один раз.

объединение результатов.

построение предварительно агрегированных материализованных представлений для общих запросов.

эта статья может помочь вам тоже: https://blog.jooq.org/2017/04/20/how-to-calculate-multiple-aggregate-functions-in-a-single-query/

with filtered as (
    select
    *
    from cars_analytic
    where
        [some search conditions]
)

--for each facet:

select
    'brand' as facet,
    brand as value,
    count(*) as count
from
    filtered
group by
    brand

union

select
    'cool-tag' as facet,
    'cool-tag'as value,
    count(*) as count
from
    filtered
where
    cool_tag

union

...


-- sort at the end
order by
    facet,
    count desc,
    value

100 000 записей с 5 фасеток в ~ 150 мс


Что касается подсчетов, зачем тянуть их через SQL? Вам все равно придется перебирать результирующий набор в коде, так почему бы не подсчитать его там?

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

этот подход предполагает, что вы отступаете список всех совпадающих элементов и, таким образом, несколько строк с одной и той же гранью. Когда вы заказываете этот результат по фасету, легко получить счетчик в вашем коде.