Fuzzy String Matching with fuzzystrmatch

In PostgreSQL, a one-character difference between strings like “hired” and “fired” can change the meaning of a query entirely. The fuzzystrmatch extension, part of the PostgreSQL contrib package, provides algorithms to handle such near-matches without requiring exact string equality.

To demonstrate, start with a small sample table:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

postgres=# CREATE TABLE t_sample (x text);

CREATE TABLE

postgres=# INSERT INTO t_sample

VALUES ('hired'), ('fired'), ('pump'), ('dump'),

       ('failure');

INSERT 0 5

postgres=# TABLE t_sample;

    x    

---------

hired

fired

pump

dump

failure

(5 rows)

Soundex Encoding

After enabling the extension, the first tool available is Soundex, which encodes words into a phonetic representation:

1

2

postgres=# CREATE EXTENSION fuzzystrmatch;

CREATE EXTENSION

1

2

3

4

5

6

7

8

9

postgres=# SELECT x, soundex(x) FROM t_sample;

    x    | soundex

---------+---------

hired   | H630

fired   | F630

pump    | P510

dump    | D510

failure | F460

(5 rows)

Because Soundex groups similar-sounding words under the same code, both "fired" and "firred" produce identical encodings—something a plain equality search would miss. The soundex function being immutable allows it to back a functional index:

1

2

3

4

5

6

7

postgres=# SELECT x, soundex(x)

FROM t_sample

WHERE soundex(x) = soundex('firred');

   x   | soundex

-------+---------

fired | F630

(1 row)

1

2

3

4

5

6

7

8

9

10

11

12

13

postgres=# CREATE INDEX ON t_sample (soundex(x));

CREATE INDEX

postgres=# SET enable_seqscan TO off;

SET

postgres=# explain SELECT x, soundex(x)

FROM t_sample

WHERE soundex(x) = soundex('firred');

                      QUERY PLAN                                      

---------------------------------------------------

Index Scan using t_sample_soundex_idx on t_sample  

(cost=0.13..8.15 rows=1 width=64)

   Index Cond: (soundex(x) = 'F630'::text)

(2 rows)

On larger tables this index can be used directly by the planner; the small sample table above is too small to trigger an index scan unless you force the planner with a runtime flag.

Levenshtein Distance

The extension also implements Levenshtein distance, which counts the minimum single-character edits required to convert one string into another. For example:

1

2

3

4

5

postgres=# SELECT levenshtein('venture capital', 'adventure game');

levenshtein

-------------

           8

(1 row)

The longer the distance, the more dissimilar the strings. You can filter query results by a tolerance threshold for typos:

1

2

3

4

5

6

7

8

postgres=# SELECT x

FROM t_sample

WHERE levenshtein(x, 'pumpy') <= 2;

  x  

------

pump

dump

(2 rows)

In that case, two rows fall within the allowed edit distance. Levenshtein is most effective when you expect minor spelling errors and have a fairly clear target in mind.

Metaphone for Length-Controlled Encoding

For more flexibility than Soundex, metaphone allows you to set the maximum length of the phonetic key:

1

2

3

4

5

postgres=# SELECT metaphone('capital structure', 4);

metaphone

-----------

KPTL

(1 row)

1

2

3

4

5

postgres=# SELECT metaphone('capital structure', 6);

metaphone

-----------

KPTLST

(1 row)

The same approach applies—encode both your search term and the column content, then compare:

1

2

3

4

5

6

postgres=# SELECT *

FROM t_sample

WHERE metaphone('open source', 3) = metaphone(x, 3);

x

---

(0 rows)

Here, no rows match because metaphone correctly determines that "open source" does not sound like "fired."

Beyond fuzzystrmatch

Community extensions such as pg_similarity offer additional fuzzy search strategies, while techniques for case-insensitive pattern matching and optimizing LIKE/ILIKE queries address other related needs. Those approaches are useful complements to the phonetic and edit-distance functions covered here.