Full Text Search

By the end of this lesson you'll be able to build real, scalable search: index your text, find documents by meaning (not just exact characters), rank the hits by relevance, and tolerate typos. You'll see exactly why LIKE '%word%' falls apart — and what professionals reach for instead.

Part of the free SQL course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

What You'll Learn

Our Sample Table: articles

Every query in this lesson searches this little articles table. The body column is shown shortened, but full-text search reads the whole thing.

1. Why Not Just LIKE '%word%' ?

You already know how to filter text with LIKE . So why does every serious app use something else for search? Because a leading-wildcard LIKE '%word%' has three problems that get worse as your data grows.

LIKE '%word%' is reading every book in the library cover to cover to find a word. Full-text search is the index at the back of each book that a librarian built ahead of time — ask for "database optimization" and she pulls the right books instantly, sorted by how relevant they are.

The fix is to pre-process text into a searchable form once, index it, and then search the index. That's what full-text search does.

2. PostgreSQL: tsvector , tsquery & @@

PostgreSQL splits search into two data types. A tsvector is the document side: your text broken into normalised root words (called lexemes ). A tsquery is the search side: the words you're looking for. The @@ operator returns true when the document matches the query.

Your Turn: complete the match

Fill in the blanks so the query finds every article whose title or body mentions index . Hint: use the friendly query builder for a single plain word. The expected result is in the comments so you can check yourself.

3. Boolean & Phrase Search

Beyond single words, to_tsquery understands a small operator language so users can be precise: combine terms, exclude terms, or demand an exact phrase.

4. Ranking Results with ts_rank

The @@ operator only answers yes/no — but real search shows the best result first. ts_rank(document, query) returns a relevance score (higher is better) based on how often the terms appear and where. Sort by it with ORDER BY rank DESC .

Listing the query once in the FROM clause ( plainto_tsquery(…) AS query ) lets you reuse it in both the SELECT and the WHERE without building it twice. Prefer ts_rank_cd when how close the words sit to each other matters.

Your Turn: add relevance ranking

The WHERE already finds the matches. Add the scoring function and the sort direction so the most relevant article comes back first.

5. Make It Fast: GIN Index & Weights

Everything so far recomputes to_tsvector() for every row on every query — fine for demos, far too slow for millions of rows. Store the vector in a column and put a GIN index on it (Generalised INverted index — the same idea as a book's back-of-book index). With setweight() you also tag where each word came from so title hits outrank body hits.

6. MySQL: MATCH() AGAINST()

MySQL takes a different route. You add a FULLTEXT index over the text columns, then search with MATCH(columns) AGAINST('terms') . Natural-language mode (the default) ranks by relevance automatically; boolean mode unlocks + / - / "phrase" operators much like Postgres' tsquery .

7. Fuzzy Matching for Typos

Full-text search needs the right word (after stemming). But users misspell things — "databse", "Samsnug". Fuzzy matching measures how similar two strings are so a near-miss still matches. Two common tools, both high-level here: trigram similarity ( pg_trgm ) and Levenshtein distance ( fuzzystrmatch ).

Common Errors (and the fix)

📘 Quick Reference

Syntax

Purpose

Frequently Asked Questions

For exact substring checks on small tables, or a prefix match like LIKE 'abc%' (no leading wildcard), which can use a normal index. For a real search box over lots of text, use full-text search.

Q: What's the difference between to_tsquery and plainto_tsquery ?

to_tsquery expects operator syntax ( 'database & !mysql' ) and errors on a bare space. plainto_tsquery takes free text from a user and ANDs the words for you — safer for search-box input.

Q: Do I need a GIN index to use full-text search?

No — it works without one, but every query rescans the table. Once your data grows, a GIN index (Postgres) or FULLTEXT index (MySQL) is what makes search fast.

Q: Full-text search vs. fuzzy matching — which do I want?

Both. Full-text finds documents about a topic (with stemming and ranking); fuzzy matching ( pg_trgm , Levenshtein) catches typos . Production search usually layers full-text first with a fuzzy fallback.

Mini-Challenge: A Ranked Search, End to End

Put it all together — a brief, a blank canvas, and the expected result in the comments. Write it, then copy it into a PostgreSQL playground to confirm.

🎉 Lesson Complete

Practice quiz

Why does a leading-wildcard LIKE '%word%' scale poorly for search?

  • It only works on numeric columns
  • It always returns too many rows
  • It cannot use a normal index, so it scans every row and every character
  • It requires a GIN index to run

Answer: It cannot use a normal index, so it scans every row and every character. Leading-wildcard LIKE forces a full table scan and cannot rank or stem results.

In PostgreSQL full-text search, what is a tsvector?

  • The document side: text broken into normalised lexemes (root words)
  • The search query side
  • A bitmap of matching rows
  • A relevance score

Answer: The document side: text broken into normalised lexemes (root words). to_tsvector turns text into a sorted list of normalised lexemes with positions.

What does the @@ operator do?

  • Concatenates two strings
  • Computes a relevance score
  • Creates a GIN index
  • Returns true when a tsvector document matches a tsquery

Answer: Returns true when a tsvector document matches a tsquery. @@ asks 'does this document match this query?' and returns a boolean.

Why does searching for 'databases' still match the word 'database'?

  • LIKE handles plurals automatically
  • Stemming reduces both to the same root lexeme (databas)
  • Stop-words are added back
  • The @@ operator ignores letters

Answer: Stemming reduces both to the same root lexeme (databas). to_tsvector stems words to roots, so singular and plural forms match.

What is the safest tsquery builder for raw user input like a typed phrase?

  • plainto_tsquery, which ANDs the words for you
  • to_tsquery, which errors on a bare space
  • to_tsvector
  • ts_rank

Answer: plainto_tsquery, which ANDs the words for you. plainto_tsquery takes free text and ANDs the words; to_tsquery errors on a plain space.

What does ts_rank provide that the @@ operator does not?

  • A yes/no match
  • Automatic indexing
  • A relevance score so you can ORDER BY rank DESC
  • Typo tolerance

Answer: A relevance score so you can ORDER BY rank DESC. @@ only answers yes/no; ts_rank scores relevance so the best result comes first.

What index type makes PostgreSQL full-text search fast on millions of rows?

  • A hash index
  • A GIN index on the tsvector column
  • A bitmap index
  • A clustered index

Answer: A GIN index on the tsvector column. A GIN (generalised inverted) index pre-stores lexemes so search jumps to matching rows.

What does setweight() do in a weighted tsvector?

  • Sets the GIN index fill factor
  • Limits the number of results
  • Removes stop-words
  • Tags lexemes A-D so a title hit outranks a body mention

Answer: Tags lexemes A-D so a title hit outranks a body mention. setweight tags where each word came from (A=title highest ... D=lowest) for better ranking.

How does MySQL perform full-text search?

  • A tsvector column with @@
  • A FULLTEXT index searched with MATCH(cols) AGAINST('terms')
  • A GIN index with ts_rank
  • Only with LIKE

Answer: A FULLTEXT index searched with MATCH(cols) AGAINST('terms'). MySQL uses a FULLTEXT index and MATCH ... AGAINST, with natural-language and boolean modes.

When should you reach for fuzzy matching (pg_trgm / Levenshtein) over full-text search?

  • When searching for an exact topic
  • When you need stemming
  • When the user typed the word slightly wrong (typos/misspellings)
  • When you want stop-words removed

Answer: When the user typed the word slightly wrong (typos/misspellings). Full-text finds the topic after stemming; fuzzy tools catch typos like 'databse'.

Continue this course

Related lessons