Full-text search & trigram similarity

Django ORM PostgreSQL May 22, 2026 python

SearchVector/SearchQuery/SearchRank are PostgreSQL full-text search; websearch parses Google-style queries (OR, quotes, -).

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# ======================================================================
# `SearchVector`/`SearchQuery`/`SearchRank` are PostgreSQL full-text search;
# `websearch` parses Google-style queries (`OR`, quotes, `-`).
# `TrigramSimilarity` (pg_trgm) is typo-tolerant fuzzy matching, and
# `__unaccent` strips diacritics so 'muller' finds 'Müller'.
# ======================================================================

# ----------------------------------------------------------------------
# websearch 'lord OR postgres' ranked
# ----------------------------------------------------------------------

# Django:
query = SearchQuery("lord OR postgres", search_type="websearch")
Book.objects.annotate(rank=SearchRank("search", query)) \
    .filter(search=query).order_by("-rank") \
    .values_list("title", "rank")

# SQL:
#   SELECT "examples_book"."title" AS "title", ts_rank(to_tsvector(COALESCE(("examples_book"."search")::text, '')), websearch_to_tsquery('lord OR postgres')) AS "rank"
#   FROM "examples_book"
#   WHERE "examples_book"."search" @@ (websearch_to_tsquery('lord OR postgres'))
#   ORDER BY 2 DESC

# Result:
#   The Lord of the Rings: 0.0304
#   Postgres for Authors: 0.0304

# ----------------------------------------------------------------------
# trigram similarity to 'potter'
# ----------------------------------------------------------------------

# Django:
Book.objects.annotate(sim=TrigramSimilarity("title", "potter")) \
    .filter(sim__gt=0.2).order_by("-sim") \
    .values_list("title", "sim")

# SQL:
#   SELECT "examples_book"."title" AS "title", SIMILARITY("examples_book"."title", 'potter') AS "sim"
#   FROM "examples_book"
#   WHERE SIMILARITY("examples_book"."title", 'potter') > 0.2
#   ORDER BY 2 DESC

# Result:
#   Harry Potter: 0.538

# ----------------------------------------------------------------------
# __unaccent: 'muller' matches 'Müller'
# ----------------------------------------------------------------------

# Django:
Author.objects.filter(name__unaccent__icontains="muller")

# SQL:
#   SELECT "examples_author"."id", "examples_author"."name", "examples_author"."bio", "examples_author"."born", "examples_author"."rating", "examples_author"."nickname"
#   FROM "examples_author"
#   WHERE UPPER(UNACCENT("examples_author"."name")::text) LIKE '%' || UPPER(REPLACE(REPLACE(REPLACE((UNACCENT('muller')), E'\\', E'\\\\'), E'%', E'\\%'), E'_', E'\\_')) || '%'

# Result:
#   Karl Müller