postgresqlelasticsearchfull-text-searchpg-trgm

Edge NGram search in PostgreSQL


I need to make search-as-you-type autocomplete for a large list of companies (over 80,000,000). The company name should contain the word that starts with a search query like this

+-------+----------------------------------+
| term  | results                          |
+-------+----------------------------------+ 
| gen   | general motors; general electric |
| geno  | genoptix; genomic health         |
| genom | genoma group; genomic health     |
+-------+----------------------------------+

The pg_trgm module and GIN index implement similar behavior but don't solve my problem.

For example, ElasticSearch has feature Edge NGram Tokenizer that completely fits my requirements.

From documentation:

The edge_ngram tokenizer first breaks the text down into words 
whenever it encounters one of a list of specified characters, 
then it emits N-grams of each word 
where the start of the N-gram is anchored to the beginning of the word.

Edge N-Grams are useful for search-as-you-type queries.

Is there a similar solution in PostgreSQL?


Solution

  • I create a custom tokenizer

    CREATE OR REPLACE FUNCTION edge_gram_tsvector(text text) RETURNS tsvector AS
    $BODY$
    BEGIN
        RETURN (select array_to_tsvector((select array_agg(distinct substring(lexeme for len)) from unnest(to_tsvector(text)), generate_series(1,length(lexeme)) len)));
    END;
    $BODY$
    IMMUTABLE
    language plpgsql;
    

    This function creates all edge ngrams like this

    postgres=# select edge_gram_tsvector('general electric');
                                   edge_gram_tsvector
    -----------------------------------------------------------------------------------------
     'e' 'el' 'ele' 'elec' 'elect' 'electr' 'g' 'ge' 'gen' 'gene' 'gener' 'genera' 'general'
    (1 row)
    

    Then I create a GIN index for tsquery

    create index on company using gin(edge_gram_tsvector(name));
    

    The search query will look like this

    b2bdb_master=# select name from company where edge_gram_tsvector(name) @@ 'electric'::tsquery limit 3;
                        name
    --------------------------------------------
     General electric
     Electriciantalk
     Galesburg Electric Industrial Supply
    (3 rows)
    

    Performance of solution is quite high


    explain analyse select * from company where edge_gram_tsvector(name) @@ 'electric'::tsquery;
    
    
    Bitmap Heap Scan on company  (cost=175.13..27450.31 rows=20752 width=2247) (actual time=0.224..1.019 rows=343 loops=1)
      Recheck Cond: (edge_gram_tsvector((name)::text) @@ '''electric'''::tsquery)
      Heap Blocks: exact=342
      ->  Bitmap Index Scan on company_edge_gram_tsvector_idx  (cost=0.00..169.94 rows=20752 width=0) (actual time=0.138..0.138 rows=343 loops=1)
            Index Cond: (edge_gram_tsvector((name)::text) @@ '''electric'''::tsquery)
    Planning Time: 0.216 ms
    Execution Time: 1.100 ms