У меня есть БД людей со столбцом jsonb interests
. В моем приложении пользователь может искать людей, указывая их хобби, которые задаются некоторыми предопределенными значениями. Я хочу предложить ему наилучшее совпадение, и для этого я бы хотел засчитать совпадение как пересечение / союз интересов. Таким образом, лучшие результаты не будут у людей, у которых много хобби в моей БД. Пример:
Записи БД:
name interests::jsonb
Mary ["swimming","reading","jogging"]
John ["climbing","reading"]
Ann ["swimming","watching TV","programming"]
Carl ["knitting"]
пользовательский ввод в приложении:
["reading", "swimming", "knitting", "cars"]
мой скрипт должен вывести это:
Mary 0.4
John 0.2
Ann 0.16667
Carl 0.25
Теперь Я использую
SELECT name
FROM people
WHERE interests @>
ANY (ARRAY ['"reading"', '"swimming"', '"knitting"', '"cars"']::jsonb[])
, но это дает мне даже записи с множеством интересов, и нет возможности их заказать. Есть ли способ добиться этого в разумные сроки - скажем, до 5 секунд в БД с примерно 400 КБ записей?
РЕДАКТИРОВАТЬ : Я добавил еще один пример, чтобы прояснить свои расчеты. Мой расчет должен отфильтровать людей с большим количеством увлечений. Поэтому совпадение должно быть рассчитано как Intersection (input, db_record) / Union (input, db_record).
Пример: input = ["reading"]
Записи DB:
name interests::jsonb
Mary ["swimming","reading","jogging"]
John ["climbing","reading"]
Ann ["swimming","watching TV","programming"]
Carl ["reading"]
Соответствие Мэри будет рассчитано как (LENGTH(["reading"]))/(LENGTH(["swimming","reading","jogging"]))
, что составляет 0,3333, а для Карла это будет (LENGTH(["reading"]))/LENGTH([("reading")])
, что равно 1
ОБНОВЛЕНИЕ : Мне удалось сделать это с помощью
SELECT result.id, result.name, result.overlap_count/(jsonb_array_length(persons.interests) + 4 - result.overlap_count)::decimal as score
FROM (SELECT t1.name as name, t1.id, COUNT(t1.name) as overlap_count
FROM (SELECT name, id, jsonb_array_elements(interests)
FROM persons) as t1
JOIN (SELECT unnest(ARRAY ['"reading"', '"swimming"', '"knitting"', '"cars"'])::jsonb as elements) as t2 ON t1.jsonb_array_elements = t2.elements
GROUP BY t1.name, t1.id) as result
JOIN persons ON result.id = persons.id ORDER BY score desc
Вот моя скрипка https://dbfiddle.uk/?rdbms=postgres_12&fiddle=b4b1760854b2d77a1c7e6011d074a1a3
Однако она недостаточно быстра, и я был бы признателен за любые улучшения.