How do you build a full-text index? (GIN on to_tsvector(.… — Cracked Java
// PostgreSQL · Full-Text Search
MidCoding

How do you build a full-text index? (GIN on to_tsvector(...).)

Without an index, every search recomputes to_tsvector on every row and scans the whole table; a GIN index makes search probe only the matching lexemes. There are two idiomatic ways to build it, and the generated-column approach is the one to reach for in PostgreSQL 17.

Option A — expression index on to_tsvector(...)

Index the exact expression you'll query with:

CREATE INDEX idx_docs_fts
  ON docs USING GIN (to_tsvector('english', body));

SELECT * FROM docs
WHERE to_tsvector('english', body) @@ to_tsquery('english', 'postgres & index');

The planner only uses the index when the query expression matches the indexed expression character-for-character (including the config name). It works, but you repeat the expression everywhere and it's easy to drift.

Option B — generated column (preferred)

Store the tsvector as a GENERATED ALWAYS column so it's maintained automatically, then index the plain column:

ALTER TABLE docs
  ADD COLUMN search_vec tsvector
  GENERATED ALWAYS AS (to_tsvector('english', coalesce(body, ''))) STORED;

CREATE INDEX idx_docs_search ON docs USING GIN (search_vec);

SELECT * FROM docs WHERE search_vec @@ to_tsquery('english', 'postgres & index');

Cleaner queries, the vector stays in sync on every insert/update, and you can combine columns (e.g. weighting title above body) inside the expression.

Why GIN, and weighting multiple columns

GIN (Generalized Inverted Index) stores one entry per lexeme pointing at all rows containing it — the inverted-index structure search needs. GiST also works but is lossy and slower for static text; GIN is the default choice. Use setweight to mark which field a lexeme came from for later ranking:

to_tsvector('english', coalesce(title,'')) -- weight A
  || to_tsvector('english', coalesce(body,''));  -- weight B
-- wrap each with setweight(..., 'A') / setweight(..., 'B')

Mark your status