【问题标题】:How to group by similar values with pg_trgm如何使用 pg_trgm 按相似值分组
【发布时间】:2017-11-09 21:55:02
【问题描述】:

我有下表

id error
-  ----------------------------------------
1  Error 1234eee5, can not write to disk
2  Error 83457qwe, can not write to disk
3  Error 72344ee, can not write to disk
4  Fatal barier breach on object 72fgsff
5  Fatal barier breach on object 7fasdfa
6  Fatal barier breach on object 73456xcc5

我希望能够得到一个按相似度计算的结果,其中 > 80% 的相似度意味着两个错误相等。我一直在使用 pg_trgm 扩展,它的相似函数对我来说非常有效,我唯一能弄清楚如何产生下面的分组结果。

Error                                  Count
-------------------------------------  ------
Error 1234eee5, can not write to disk, 3
Fatal barier breach on object 72fgsff, 3

【问题讨论】:

    标签: postgresql pg-trgm


    【解决方案1】:

    基本上,您可以将一个表与自身连接起来以查找相似的字符串,但是这种方法会导致对较大数据集的查询非常缓慢。此外,在某些情况下使用similarity() 可能会导致不准确(您需要找到合适的限制值)。

    您应该尝试寻找模式。例如,如果字符串中的所有变量词都以数字开头,您可以使用 regexp_replace(): 屏蔽它们

    select id, regexp_replace(error, '\d\w+', 'xxxxx') as error
    from errors;
    
     id |                error                
    ----+-------------------------------------
      1 | Error xxxxx, can not write to disk
      2 | Error xxxxx, can not write to disk
      3 | Error xxxxx, can not write to disk
      4 | Fatal barier breach on object xxxxx
      5 | Fatal barier breach on object xxxxx
      6 | Fatal barier breach on object xxxxx
    (6 rows)    
    

    这样您就可以轻松地按错误消息对数据进行分组:

    select regexp_replace(error, '\d\w+', 'xxxxx') as error, count(*)
    from errors
    group by 1;
    
                    error                | count 
    -------------------------------------+-------
     Error xxxxx, can not write to disk  |     3
     Fatal barier breach on object xxxxx |     3
    (2 rows)
    

    以上查询只是一个示例,具体解决方案取决于数据格式。

    使用 pg_trgm

    基于 OP 想法的解决方案(参见下面的 cmets)。 similarity() 的限制 0.8 肯定太高了。看起来应该是 0.6 左右。

    unique 错误的表(我使用了一个临时表,但它当然也是一个常规表):

    create temp table if not exists unique_errors(
        id serial primary key, 
        error text, 
        ids int[]);
    

    ids 列用于存储基表中包含类似错误的行的id

    do $$
    declare
        e record;
        found_id int;
    begin
        truncate unique_errors;
        for e in select * from errors loop
            select min(id)
            into found_id
            from unique_errors u
            where similarity(u.error, e.error) > 0.6;
            if found_id is not null then
                update unique_errors
                set ids = ids || e.id
                where id = found_id;
            else
                insert into unique_errors (error, ids)
                values (e.error, array[e.id]);
            end if;
        end loop;
    end $$;
    

    最终结果:

    select *, cardinality(ids) as count
    from unique_errors;
    
     id |                 error                 |   ids   | count 
    ----+---------------------------------------+---------+-------
      1 | Error 1234eee5, can not write to disk | {1,2,3} |     3
      2 | Fatal barier breach on object 72fgsff | {4,5,6} |     3
    (2 rows)
    

    【讨论】:

    • 感谢您的回答,但此解决方案对我不起作用,因为它不够通用。上面的查询需要维护 sql 查询,实际上有 100 个可能的错误,上面只是一个例子。我可以很容易地在 python 中做到这一点,只是不熟悉 sql 循环结构来写出来。就像:unique_errors = [] for row in "select error in errors":
    • 我可以很容易地在 python 中做到这一点,只是不熟悉 sql 循环结构来写出来。这个想法是循环每个错误,与 unique_errors 列表进行比较,如果它在唯一列表中达到 80% 增量计数的相似性,如果没有任何东西高于 80% 相似度,则将其添加到唯一列表中,计数为 1,并且继续下一个错误,依此类推,直到完成。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-05-16
    • 1970-01-01
    • 2022-08-10
    • 1970-01-01
    • 2011-01-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多