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

Posted

技术标签:

【中文标题】在 Postgres 中,如何匹配多个“标签”以获得最佳性能?【英文标题】:In Postgres, how to match multiple "tags" for best performance? 【发布时间】:2016-12-24 07:11:39 【问题描述】:

表格:文章

+--------+------+------------+
| 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篇文章的最佳索引设计和查询是什么? 我知道有几种方法可以做到这一点,但我关心的是性能,考虑到每个标签中可能有数万篇文章

【问题讨论】:

query to select latest 10 articles - 请解释你如何定义latest article ?某些表中是否有日期列,但问题中未显示?还是the latest 表示id 列中的最高值? @krokodilko 我在表格中添加了“已创建”列。是的,最新的是 id 列中的最高值。 id 是“int serial”。 这可能对你来说很有趣:databasesoup.com/2015/01/tag-all-things.html 【参考方案1】:

正如a_horse_with_no_name 提到的,这篇博文有一些非常有趣的性能基准,用于查找匹配多个标签的行:

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

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

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 倍。

【讨论】:

自 2015 年以来有变化吗? 最好用最新版本的 PG 进行新测试。有什么引用吗?【参考方案2】:

您需要在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】:
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 子句以使用“包含”运算符:

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;

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

【讨论】:

【参考方案4】:

就我而言,我必须对每个“标签”应用一个复杂的条件,而@> 没有用。所以我找到了另一种方法:grid sensor array:

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]

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

【讨论】:

以上是关于在 Postgres 中,如何匹配多个“标签”以获得最佳性能?的主要内容,如果未能解决你的问题,请参考以下文章

postgres查询按AND部分匹配排序,然后OR匹配

如何使用动态正则表达式匹配 Postgres 中的值

如何在 Postgres 函数中检索多个结果行?

如何按postgres数组中的多个ID分组

如何找到匹配多个条件的元素

Rails 3 路由如何匹配多个?