在Postgres中,如何匹配多个“标签”以获得最佳性能?

时间:2016-12-24 07:11:40

标签: sql postgresql relational-division

表:文章

+--------+------+------------+
| id     | title|    created |
+--------+------+------------+
|    201 | AAA  | 1482561011 |
|    202 | BBB  | 1482561099 |
|    203 | CCC  | 1482562188 |
+--------+------+------------+

表:标签

+-----------+------+
| articleid | tagid|
+-----------+------+
|    201    | 11   |
|    201    | 12   |
|    202    | 11   |
|    202    | 13   |
|    202    | 14   |
+-----------+------+

现在,如果给出 3个标签ID ,那么选择每篇文章同时匹配3个标签ID 的最新10篇文章的最佳索引设计和查询是什么?
我知道有几种方法可以做到,但我关注性能,考虑到每个标签中可能有成千上万篇文章

4 个答案:

答案 0 :(得分:1)

select distinct on (a.id) a.*
from articles a 
  join taggings t on t.articleid = a.id
group by a.id 
having array_agg(t.tagid order by t.tagid) = array[11,13,14]
order by a.id, a.created
limit 10;

taggings (articleid, tagid)上的索引对此有帮助。

请注意,上面的内容会查找完全这三个标记的文章。如果你想找到至少这三个标签(可能还有更多)的那些,你可以改变having子句来使用" contains"操作者:

select distinct on (a.id) a.*
from articles a 
  join taggings t on t.articleid = a.id
where t.tagid in (11,13,14)
group by a.id 
having array_agg(t.tagid) @> array[11,13,14]
order by a.id, a.created
limit 10;

在这种情况下,order by array_agg()不是必需的

答案 1 :(得分:1)

a_horse_with_no_name所述,此博客文章具有一些非常有趣的性能基准,用于查找与多个标签匹配的行:

http://www.databasesoup.com/2015/01/tag-all-things.html

将标签存储在主表的数组列中并创建GIN-index允许像这样选择行,而无需任何连接:

select id
from articles
where tags @> array[11,13,14]
order by created desc
limit 10;

可以像这样创建列和索引:

alter table articles add column tags text[] not null default '{}';
create index tags_index on articles using gin (tags);

根据博客报道,使用数组列的速度比加入标签表的速度快8到895倍。

答案 2 :(得分:0)

您需要在articles.created上设置索引进行排序,并在taggings(articleid, tagid)上使用另一个唯一索引进行查询:

CREATE INDEX ON articles(created);
CREATE UNIQUE INDEX ON taggings(articleid, tagid);

然后只使用三个taggings表别名进行选择查询:

SELECT a.* FROM articles a, taggings t1, taggings t2, taggings t3
    WHERE a.id=t1.articleid AND a.id=t2.articleid AND a.id=t3.articleid
    AND t1.tagid=111 AND t2.tagid=222 AND t3.tagid=333
    ORDER BY created DESC LIMIT 10;

答案 3 :(得分:0)

就我而言,我必须对每个“标签”应用一个复杂的条件,而@>毫无用处。所以我找到了另一种方法:一个网格传感器数组:

ARRAY[
  bool_or(true if tag1 is present), 
  bool_or(true if tag2 is present),
  ...
] = ARRAY[true, true, ...]

示例:

SELECT a.*
FROM articles a JOIN tags t ON(t.articleid = a.id)
GROUP BY a.id
HAVING 
  ARRAY[
    bool_or(t.tagid == 11),
    bool_or(t.tagid == 13),
    bool_or(t.tagid == 14)
  ] == ARRAY[true, true, true]

它的性能很差,但对于许多很多关系却具有很大的灵活性。