【问题标题】:In Postgres, how to match multiple "tags" for best performance?在 Postgres 中,如何匹配多个“标签”以获得最佳性能?
【发布时间】:2023-04-02 08:01:01
【问题描述】:

表格:文章

+--------+------+------------+
| 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

标签: sql postgresql relational-division


【解决方案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]
      

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-02-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-02-12
        • 2015-05-17
        • 1970-01-01
        相关资源
        最近更新 更多