Повторное исследование совокупной доходности топ-5 в каждой группе

Предположим, у меня есть документы в таком формате:

product_name TEXT tags TAG score NUMERIC 

[product1, [tag1, tag2, tag3], 10]
[product2, [tag2, tag3, tag4], 100]
....

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

[tag3, 110, [product2, product 1]]
[tag2, 110, [product2, product 1]]
[tag4, 100, [product2]]
[tag1, 10, [product 1]]

То, что у меня есть до сих пор, - это хранить каждый ключ продукта/тега отдельно (повторяется для каждого тега), поэтому для каждого продукта у нас есть отдельный документ для каждого тега, а идентификатор представляет собой комбинацию имени продукта и тега: product_name TEXT tag TAG score NUMERIC. Теперь я могу запустить агрегированный запрос, чтобы получить список лучших тегов:

FT.AGGREGATE product_tags * 
   GROUP BY 1 @TAG 
     REDUCE SUM 1 @score as total_score
   SORT BY 2 @total_score DESC

Это даст мне лучшие теги по порядку, но если я хочу получить 5 лучших продуктов для каждого тега, который я нашел, есть только REDUCE TOLIST 1 @product_name, который вернет все не отсортированные продукты, и есть REDUCE FIRST_VALUE 4 @product_name BY @score DESC, который вернет только первый лучший продукт.

Есть ли способ получить, скажем, 5 лучших продуктов для каждого тега в одном запросе. Если нет, то возможно ли изменить формат хранения документов (или добавить дополнительный) таким образом, чтобы сделать этот тип запроса возможным или с минимальным количеством запросов?

Не имеет значения, но я использую клиент Redisearch для Python.


person user12177990    schedule 30.12.2019    source источник
comment
вам нужен чистый код Python о том, как это сделать?   -  person Ch3steR    schedule 30.12.2019
comment
Достаточно командной строки Redisearch или просто алгоритма или объяснения, как это сделать.   -  person user12177990    schedule 30.12.2019
comment
Я не знаком с повторным исследованием, но если вам нужен чистый код Python, я опубликую его ниже. Может быть, это даст представление.   -  person Ch3steR    schedule 30.12.2019


Ответы (1)


Первый:

  • Обязательно отключите функции, которые вы не будете использовать (NOOFFSETS, NOHL, NOFREQS, STOPWORDS 0).
  • Используйте SORTABLE для NUMERIC score.

Вот схема, которую я использовал для тестирования:

FT.CREATE product_tags NOOFFSETS NOHL NOFREQS STOPWORDS 0
    SCHEMA product_name TEXT tags TAG score NUMERIC SORTABLE

Вы хотите думать о FT.AGGREGATE как о конвейере.

Первым шагом будет сортировка продуктов по @score, чтобы позже, в конце конвейера, когда мы REDUCE TOLIST 1 @product_name, список вышел отсортированным:

SORTBY 2 @score DESC

Я думаю, что вы уже делаете LOAD/APPLY для работы с тегами, поскольку поля TAG в противном случае были бы сгруппированы по полному списку строковых тегов, разделенных запятыми, для каждого продукта. См. раздел Разрешить GROUPBY для проблем с полями тегов. Итак, наш следующий шаг в разработке:

LOAD 1 @tags 
APPLY split(@tags) as TAG 

Затем мы группируем по @TAG и применяем два сокращения. Наш список продуктов будет отсортирован.

GROUPBY 1 @TAG
    REDUCE SUM 1 @score AS total_score
    REDUCE TOLIST 1 @product_name AS products

Наконец, мы сортируем по @total_score:

SORTBY 2 @total_score DESC

Вот окончательный вид команды:

FT.AGGREGATE product_tags *
    SORTBY 2 @score DESC 
    LOAD 1 @tags 
    APPLY split(@tags) as TAG
    GROUPBY 1 @TAG
        REDUCE SUM 1 @score AS total_score 
        REDUCE TOLIST 1 @product_name AS products
    SORTBY 2 @total_score DESC

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

> FT.CREATE product_tags NOOFFSETS NOHL NOFREQS STOPWORDS 0 SCHEMA product_name TEXT tags TAG score NUMERIC SORTABLE
OK
> FT.ADD product_tags pt:product10 1 FIELDS product_name product10 tags tag2,tag3,tag4 score 10
OK
> FT.ADD product_tags pt:product1 1 FIELDS product_name product1  tags tag1,tag2,tag3 score 1
OK
> FT.ADD product_tags pt:product100 1 FIELDS product_name product100 tags tag2,tag3 score 100
OK
> FT.ADD product_tags pt:product5 1 FIELDS product_name product5 tags tag1,tag4 score 5
OK
> FT.SEARCH product_tags *
1) (integer) 4
2) "pt:product5"
3) 1) "product_name"
   2) "product5"
   3) "tags"
   4) "tag1,tag4"
   5) "score"
   6) "5"
4) "pt:product100"
5) 1) "product_name"
   2) "product100"
   3) "tags"
   4) "tag2,tag3"
   5) "score"
   6) "100"
6) "pt:product1"
7) 1) "product_name"
   2) "product1"
   3) "tags"
   4) "tag1,tag2,tag3"
   5) "score"
   6) "1"
8) "pt:product10"
9) 1) "product_name"
   2) "product10"
   3) "tags"
   4) "tag2,tag3,tag4"
   5) "score"
   6) "10"
> FT.AGGREGATE product_tags * SORTBY 2 @score DESC LOAD 1 @tags APPLY split(@tags) as TAG GROUPBY 1 @TAG REDUCE SUM 1 @score AS total_score REDUCE TOLIST 1 @product_name AS products SORTBY 2 @total_score DESC
1) (integer) 4
2) 1) "TAG"
   2) "tag2"
   3) "total_score"
   4) "111"
   5) "products"
   6) 1) "product100"
      2) "product10"
      3) "product1"
3) 1) "TAG"
   2) "tag3"
   3) "total_score"
   4) "111"
   5) "products"
   6) 1) "product100"
      2) "product10"
      3) "product1"
4) 1) "TAG"
   2) "tag4"
   3) "total_score"
   4) "15"
   5) "products"
   6) 1) "product10"
      2) "product5"
5) 1) "TAG"
   2) "tag1"
   3) "total_score"
   4) "6"
   5) "products"
   6) 1) "product5"
      2) "product1"

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

Вы можете ограничиться топ-5 с помощью Lua-скрипта:

eval "local arr = redis.call('FT.AGGREGATE', KEYS[1], '*', 'SORTBY', '2', '@score', 'DESC', 'LOAD', '1', '@tags', 'APPLY', 'split(@tags)', 'as', 'TAG', 'GROUPBY', '1', '@TAG', 'REDUCE', 'SUM', '1', '@score', 'AS', 'total_score', 'REDUCE', 'TOLIST', '1', '@product_name', 'AS', 'products', 'SORTBY', '2', '@total_score', 'DESC') \n for i=2,(arr[1]+1) do \n arr[i][6] = {unpack(arr[i][6], 1, ARGV[1])} \n end \n return arr" 1 product_tags 5

Вот дружественный вид сценария Lua выше:

local arr = redis.call('FT.AGGREGATE', KEYS[1], ..., 'DESC')
for i=2,(arr[1]+1) do 
    arr[i][6] = {unpack(arr[i][6], 1, ARGV[1])}
end
return arr

Мы передаем один ключ (индекс) и один аргумент (лимит для топовых товаров, в вашем случае 5): 1 product_tags 3.

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

person LeoMurillo    schedule 31.12.2019
comment
Вы получаете полный список отсортированных продуктов, а не только топ-5. С точки зрения сложности это не имеет значения, мы заплатили цену. Я думаю, это влияет на буферизацию и полезную нагрузку сети. - person LeoMurillo; 31.12.2019
comment
Спасибо, это здорово! Не знал о сортировке и разделении тегов. Я бы хотел, чтобы была агрегатная функция для возврата верхнего N. Меня беспокоит возврат всего списка, хотя причина потребления памяти/сети, потому что некоторые теги могут быть в большинстве продуктов, и это похоже на возврат всех названий продуктов, скажем, если это закончилось 1000000 так что не будет практично. - person user12177990; 01.01.2020
comment
@user12177990 user12177990 добавил Lua-скрипт в ответ, чтобы ограничить топ-5 в ответе. По-прежнему платите за память, но экономите полезную нагрузку сети и нагрузку на клиентов. Посмотрим, заставлю ли я вас принять ответ :-) - person LeoMurillo; 02.01.2020
comment
Спасибо, что покопались в этом, но я думаю, что моя команда не примет PR с таким количеством хаков, поэтому нужно найти другое решение или переделать это. Я создал задачу здесь: github.com/RediSearch/RediSearch/issues/1035 - person user12177990; 02.01.2020
comment
@user12177990 user12177990 - тебе трудно угодить :-) Lua-скрипты - это не хак. Lua для Redis — это то же самое, что T-SQL для SQL Server. Итак, вот мои последние два цента: вы можете сохранить набор с тегами и отсортированный набор по тегу, в котором каждый продукт добавляется с его оценкой. Вы можете получить сумму баллов для каждого тега, как в stackoverflow.com/questions/4846243/, сценарий Lua мог пройти по каждому тегу, получить сумму и первые пять. Поскольку он делает это для каждого тега, вы не перегружаете память сервера. - person LeoMurillo; 02.01.2020