Episode ini membahas Full-Text Search native dengan to_tsvector dan to_tsquery, stemming dan stop words, ranking hasil dengan ts_rank dan GIN index, serta pengenalan extension pgvector untuk similarity search dan AI embeddings dengan operator L2 dan cosine distance.

Selamat datang di episode 17 series Belajar SQL PostgreSQL! Pada episode 5 kita menggunakan ILIKE '%kata%' untuk pencarian teks. Ia bekerja — tapi ada batasnya: lambat di tabel besar, tidak bisa mengurutkan berdasarkan relevansi, dan tidak memahami bahwa "berlari" dan "berlari cepat" adalah konsep terkait. Untuk pencarian yang serius, PostgreSQL punya mesin Full-Text Search (FTS) yang bawaan dan sangat matang.
Dan ada satu lagi yang membuat PostgreSQL semakin relevan di era AI: extension pgvector. Ia mengubah PostgreSQL menjadi vector database — tempat menyimpan embeddings dan melakukan similarity search untuk aplikasi RAG (Retrieval-Augmented Generation), rekomendasi, dan pencarian semantik. Kalian tidak perlu database terpisah untuk membangun aplikasi AI.
Di episode ini, kita akan membahas FTS native dengan to_tsvector dan to_tsquery, konsep stemming dan stop words, ranking hasil pencarian dengan ts_rank, akselerasi dengan GIN index, lalu mengenal pgvector dengan operator L2 distance dan cosine distance untuk similarity search.
Inti FTS adalah mengubah teks mentah menjadi tsvector — representasi kata yang sudah dinormalisasi (huruf kecil, akar kata, tanpa kata umum). Pencarian lalu mencocokkan tsquery terhadap tsvector tersebut.
SELECT to_tsvector('indonesian',
'Saya berlari sangat cepat di pagi hari');Hasilnya adalah daftar lexeme — bentuk akar kata yang sudah dibersihkan. Kata "berlari" dan "berlari cepat" menjadi lexeme yang sama. Inilah kekuatan FTS yang tidak dimiliki LIKE.
to_tsquery mengubah string query menjadi tsquery dengan operator logika & (AND), | (OR), dan ! (NOT):
SELECT
to_tsvector('indonesian', 'Saya belajar SQL dan database') @@
to_tsquery('indonesian', 'sql & database') AS cocok;Operator @@ mengembalikan TRUE jika tsvector cocok dengan tsquery. Untuk pencarian kata tanpa logika ketat, fungsi plainto_tsquery mengubah input user biasa menjadi tsquery dengan AND otomatis.
SELECT id, title
FROM articles
WHERE to_tsvector('indonesian', title || ' ' || body)
@@ plainto_tsquery('indonesian', 'cara belajar postgresql');Dua konsep yang membuat FTS cerdas:
PostgreSQL punya konfigurasi bahasa bawaan (english, indonesian, dan lainnya). Dengan 'indonesian', stop words bahasa Indonesia otomatis dibuang dan stemming bahasa Indonesia diterapkan. Inilah alasan FTS jauh mengungguli pencarian LIKE berbasis karakter.
Tip
Jangan lupa menentukan bahasa di to_tsvector('indonesian', ...) dan to_tsquery('indonesian', ...). Jika dihilangkan, PostgreSQL memakai konfigurasi default_text_search_config — biasanya english, sehingga stemming dan stop words bahasa Inggris yang dipakai, hasil pencarian bahasa Indonesia jadi kurang optimal.
Hasil pencarian FTS berupa himpunan — tapi pengguna butuh urutan berdasarkan relevansi. Fungsi ts_rank (atau ts_rank_cd untuk coverage density) menghitung skor seberapa baik dokumen cocok dengan query:
SELECT
title,
ts_rank(
to_tsvector('indonesian', title || ' ' || body),
plainto_tsquery('indonesian', 'belajar postgresql')
) AS skor
FROM articles
WHERE to_tsvector('indonesian', title || ' ' || body)
@@ plainto_tsquery('indonesian', 'belajar postgresql')
ORDER BY skor DESC;Dokumen yang memuat kata lebih sering dan lebih awal mendapat skor lebih tinggi. Menyimpan tsvector sebagai generated column (episode 12) akan membuat query ini jauh lebih cepat dan rapi:
ALTER TABLE articles
ADD COLUMN search_vector TSVECTOR
GENERATED ALWAYS AS (
to_tsvector('indonesian', title || ' ' || body)
) STORED;Tanpa index, setiap pencarian memindai semua baris. Dengan GIN index pada kolom tsvector, pencarian FTS menjadi Index Scan (konsep episode 14):
CREATE INDEX idx_articles_search ON articles USING GIN (search_vector);Sekarang query WHERE search_vector @@ plainto_tsquery(...) memakai GIN index dan berjalan dalam milidetik bahkan di tabel dengan jutaan artikel.
Warning
Perhatikan perbedaan ekspresi antara index dan query: GIN index dibangun pada kolom search_vector — jadi query harus menyaring search_vector (atau ekspresi yang identik), bukan menulis ulang to_tsvector(...) dengan format yang berbeda. Ekspresi yang tidak sama persis akan mengakibatkan full scan lagi.
Sekarang bagian yang paling menarik di era AI: pgvector. Extension ini menambahkan tipe data vector ke PostgreSQL beserta operator untuk similarity search — mengubah database relasional menjadi vector database untuk AI embeddings.
CREATE EXTENSION IF NOT EXISTS vector;CREATE TABLE documents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
content TEXT NOT NULL,
embedding VECTOR(1536) NOT NULL
);VECTOR(1536) menandakan dimensi embedding — menyesuaikan model embedding yang dipakai (misal OpenAI text-embedding-3-large menghasilkan 3072 dimensi, model umum lainnya 768 atau 1536). Kolom ini diisi oleh aplikasi dari hasil model embedding.
pgvector menyediakan tiga operator jarak utama:
| Operator | Metode | Makna |
|---|---|---|
<-> | L2 distance (Euclidean) | Jarak geometris |
<=> | Cosine distance | Kemiripan arah vektor (paling umum untuk teks) |
<#> | Inner product | Perkalian titik |
SELECT id, content, embedding <=> $1 AS jarak
FROM documents
ORDER BY embedding <=> $1
LIMIT 5;$1 adalah placeholder untuk embedding query dari aplikasi. Hasilnya: 5 dokumen dengan jarak terkecil — yaitu paling mirip secara semantik dengan pertanyaan user.
Pipeline lengkap aplikasi RAG (Retrieval-Augmented Generation) memanfaatkan pgvector sebagai berikut:
VECTOR(1536).Semua langkah retrieval ini terjadi di PostgreSQL — tanpa infrastruktur vector database terpisah.
Note
Untuk pencarian vektor yang skala besar, buat index HNSW atau IVFFlat dengan CREATE INDEX ... USING HNSW (embedding vector_cosine_ops);. Keduanya adalah approximate nearest neighbor index yang mempercepat pencarian dari O(N) menjadi sub-linear — trade-off antara kecepatan dan akurasi yang bisa diset.
| Kebutuhan | Alat terbaik |
|---|---|
| Pencarian kata tepat, stemming bahasa, ranking relevansi | FTS |
| Pencarian semantik, dokumen mirip konsep, AI/RAG | pgvector |
| Kombinasi keyword + semantik | Keduanya (gabung dengan skor gabungan) |
Banyak aplikasi modern menggabungkan keduanya: FTS untuk pencarian kata kunci yang presisi, pgvector untuk "yang mirip" — lalu menggabungkan skor dengan ts_rank dan cosine distance.
Inti yang harus dibawa pulang:
LIKE.'indonesian').pgvector mengubah PostgreSQL menjadi vector database untuk AI embeddings.<-> (L2) dan <=> (cosine) adalah operator similarity utama; cosine paling cocok untuk teks.Di episode 18 selanjutnya, kita menangani data raksasa: Table Partitioning untuk Data Skala Besar — mulai dari kapan perlu partitioning, declarative partitioning dengan Range, List, dan Hash, hingga partition pruning yang membuat query hanya menyentuh partisi yang relevan.