SQL-запрос для поиска по нескольким тегам с сортировкой по релевантности - PullRequest
6 голосов
/ 10 октября 2011

У меня есть набор городов, которые имеют отношение многие ко многим с набором тегов.Пользователь дает мне коллекцию тегов (, которая может содержать дубликаты! ), и мне нужно вернуть список подходящих записей, отсортированных по релевантности.

Данные

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

Города:

--------------------
| id |    city     |
--------------------
|  1 |  Atlanta    |
|  2 |  Baltimore  |
|  3 |  Cleveland  |
|  4 |  Denver     |
|  5 |  Eugene     |
--------------------

Метки:

------
| id |
------
|  1 |
|  2 |
|  3 |
|  4 |
------

Города помечены так:

Atlanta:   1, 2
Baltimore: 3
Cleveland: 1, 3, 4
Denver:    2, 3
Eugene:    1, 4

... так выглядит таблица CityTags:

------------------------
|  city_id  |  tag_id  |
------------------------
|     1     |     1    |
|     1     |     2    |
|     2     |     3    |
|     3     |     1    |
|     3     |     3    |
|     3     |     4    |
|     4     |     2    |
|     4     |     3    |
|     5     |     1    |
|     5     |     4    |
------------------------

Пример 1

Если пользователь дает мнеидентификаторы тегов: [1, 3, 3, 4], я хочу подсчитать, сколько совпадений у меня есть для каждого из тегов, и вернуть отсортированный по релевантности результат, например:

------------------------
|    city    | matches |
------------------------
|  Cleveland |    4    |
|  Baltimore |    2    |
|  Eugene    |    2    |
|  Atlanta   |    1    |
|  Denver    |    1    |
------------------------

Поскольку Кливленд совпал со всемиза четырьмя метками, за ним следуют Балтимор и Юджин, у каждого из которых есть совпадение по двум меткам и т. д.

Пример 2

Еще один пример, который можно привести в качестве примера.Для поиска [2, 2, 2, 3, 4] мы получили бы:

------------------------
|    city    | matches |
------------------------
|  Denver    |    4    |
|  Atlanta   |    3    |
|  Cleveland |    2    |
|  Baltimore |    1    |
|  Eugene    |    1    |
------------------------

SQL

Если я игнорирую повторяющиеся теги, то это тривиально:

SELECT name,COUNT(name) AS relevance FROM
  (SELECT name FROM cities,citytags 
    WHERE id=city_id AND tag_id IN (1,3,3,4)) AS matches
  GROUP BY name ORDER BY relevance DESC;

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

Решение в Postgresql

Ага!Временная таблица была мне нужна.Postgresql позволяет мне делать это с помощью синтаксиса WITH.Вот решение:

WITH search(tag) AS (VALUES (1), (3), (3), (4))
SELECT name, COUNT(name) AS relevance FROM cities
INNER JOIN citytags ON cities.id=citytags.city_id
INNER JOIN search ON citytags.tag_id=search.tag
GROUP BY name ORDER BY relevance DESC;

Большое спасибо тем, кто ответил.

Ответы [ 2 ]

3 голосов
/ 10 октября 2011

Если список пользователей входит в список через запятую, вы можете попробовать превратить его в временную таблицу и присоединиться к ней. Я не знаю релевантного синтаксиса для PosteGRE, так что вот идея в MySql:

create temporary table usertags (tag_id int);
insert usertags values (1),(3),(3),(4);

SELECT name, COUNT(name) AS relevance
FROM cities
JOIN citytags on cities.id = citytags.city_id
JOIN usertags on citytags.tag_id = usertags.tag_id
GROUP BY name ORDER BY relevance DESC;

Преобразовать разделенный запятыми список в приведенный выше код будет так же просто, как заменить все , на ),(, используя ваш серверный язык, а затем внедрить его в оператор VALUES для заполнения временная таблица.

Демо (MySql): http://www.sqlize.com/1qNThhD9tC

1 голос
/ 10 октября 2011

Вставьте все теги в таблицу, а затем ПРИСОЕДИНЯЙТЕСЬ вместо того, чтобы включать их в список IN.

CREATE TABLE #input (
  tag_id INT NOT NULL
)
;

INSERT INTO #input
          SELECT 1
UNION ALL SELECT 3
UNION ALL SELECT 3
UNION ALL SELECT 4
;

SELECT
  city.name,
  search.relevance
FROM
  city
INNER JOIN
(
  SELECT
    city_id,
    COUNT(*) AS relevance
  FROM
    citytags
  INNER JOIN
    #input
      ON #input.tag_id = citytags.tag_id
  GROUP BY
    city_id
)
  AS search
    ON search.city_id = city.id
ORDER BY
  search.relevance DESC
;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...