用于通过相关性排序来搜索多个标签的SQL查询

Eri*_* R. 6 sql postgresql

我有一组与一组标签有多对多关系的城市.用户给了我一组标签(可能包含重复项!),我需要返回一个匹配条目列表,按相关性排序.

数据

以下是一些示例数据来说明问题:

城市:

--------------------
| id |    city     |
--------------------
|  1 |  Atlanta    |
|  2 |  Baltimore  |
|  3 |  Cleveland  |
|  4 |  Denver     |
|  5 |  Eugene     |
--------------------
Run Code Online (Sandbox Code Playgroud)

标签:

------
| id |
------
|  1 |
|  2 |
|  3 |
|  4 |
------
Run Code Online (Sandbox Code Playgroud)

城市标记如下:

Atlanta:   1, 2
Baltimore: 3
Cleveland: 1, 3, 4
Denver:    2, 3
Eugene:    1, 4
Run Code Online (Sandbox Code Playgroud)

...所以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    |
------------------------
Run Code Online (Sandbox Code Playgroud)

例1

如果用户给我标签ID:[1,3,3,4],我想计算每个标签的匹配数,并返回相关性排序结果,如:

------------------------
|    city    | matches |
------------------------
|  Cleveland |    4    |
|  Baltimore |    2    |
|  Eugene    |    2    |
|  Atlanta   |    1    |
|  Denver    |    1    |
------------------------
Run Code Online (Sandbox Code Playgroud)

由于克利夫兰匹配所有四个标签,它是第一个,其次是巴尔的摩和尤金,每个都有两个标签匹配,等等.

例2

另一个做出良好衡量的例子.对于搜索[2,2,2,3,4],我们得到:

------------------------
|    city    | matches |
------------------------
|  Denver    |    4    |
|  Atlanta   |    3    |
|  Cleveland |    2    |
|  Baltimore |    1    |
|  Eugene    |    1    |
------------------------
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

但那不是我需要的.我需要尊重重复.有人可以建议我怎么做到这一点?

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;
Run Code Online (Sandbox Code Playgroud)

非常感谢那些回答的人.

mel*_*okb 5

如果用户列表以逗号分隔的列表形式出现,您可以尝试将其转换为临时表并加入该表。我不知道 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;
Run Code Online (Sandbox Code Playgroud)

将逗号分隔的列表转换为上面的代码就像使用服务器端语言进行全部替换一样简单,),(然后将其嵌入到VALUES语句中以填充临时表。

演示(MySql):http://www.sqlize.com/1qNThhD9tC