Topik ini menutup modul dengan pencarian teks penuh, kemampuan bawaan PostgreSQL untuk mencari dokumen menurut kata yang dinormalkan, alih-alih pencocokan teks mentah dengan LIKE. Tiga bagian pokoknya adalah tsvector yang menyimpan kata terindeks, tsquery yang menyatakan istilah carian, dan ts_rank yang menyusun urutan hasilnya.
Blok penyiapan dataset#
# dataset artikel perpustakaan untuk latihan pencarian
PGOPTIONS='-c client_min_messages=warning' psql -q -U postgres -d perpustakaan <<'SQL'
DROP TABLE IF EXISTS artikel CASCADE;
CREATE TABLE artikel (
id SERIAL PRIMARY KEY,
judul TEXT NOT NULL,
isi TEXT NOT NULL
);
INSERT INTO artikel (judul, isi) VALUES
('Mengelola koleksi', 'Perpustakaan mengelola koleksi buku dengan katalog yang teratur dan penataan yang mudah dijangkau anggota.'),
('Jadwal peminjaman', 'Anggota dapat mengajukan peminjaman melalui jadwal layanan yang ditetapkan pustakawan setiap pekan.'),
('Kelas literasi', 'Kelas literasi informasi mengajarkan cara menilai sumber bacaan sebelum memakainya untuk tugas.'),
('Pemeliharaan rak', 'Pemeliharaan rak menuntut kelembapan yang terjaga agar koleksi buku tidak cepat rusak.');
SQLEmpat artikel pendek menyediakan kata yang bertumpang tindih, seperti koleksi dan buku, sehingga perbedaan pencarian mentah dan pencarian dinormalkan terlihat.
tsvector dan tsquery#
to_tsvector mengubah teks menjadi daftar kata terleksikalisasi (lexeme) beserta posisinya, sedangkan to_tsquery menyatakan istilah carian dalam bentuk yang sama, dan operator @@ mencocokkan keduanya. Blok berikut mencari artikel yang memuat kata koleksi dalam bentuk apa pun.
# pencarian kata yang dinormalkan
psql -U postgres -d perpustakaan <<'SQL'
SELECT to_tsvector('indonesian', 'koleksi-koleksi buku di koleksi') AS vektor;
SELECT judul FROM artikel WHERE to_tsvector('indonesian', isi) @@ to_tsquery('indonesian', 'koleksi') ORDER BY id;
SQL vektor
-----------------------------------------------------
'buku':4 'di':5 'koleksi':2,3,6 'koleksi-koleksi':1
(1 row)
judul
-------------------
Mengelola koleksi
Pemeliharaan rak
(2 rows)Konfigurasi indonesian menormalkan kata berulang menjadi satu bentuk leksikal, sehingga pencariannya menemukan dokumen yang memuat bentuk-bentuk terkaitnya tanpa daftar kata yang ditulis manual. Pembandingan LIKE ‘%koleksi%’ tidak melayani variasi bentuk kata dan tidak dapat memakai index untuk awalan liar, dan inilah alasan pencarian serius memakai jenis data ini.
Kolom tsvector tersimpan dan diindeks#
Menghitung to_tsvector pada setiap pencarian membaca seluruh tabel, dan pola yang benar menyimpan vektornya sebagai kolom yang terhitung otomatis, lalu menindeksnya dengan GIN. Blok berikut menambahkan kolom carian berupa kolom terhitung, mengindeksnya, dan mencari lewat kolom tersebut.
# kolom terhitung tsvector dengan index GIN
psql -U postgres -d perpustakaan <<'SQL'
ALTER TABLE artikel ADD COLUMN carian TSVECTOR
GENERATED ALWAYS AS (to_tsvector('indonesian', isi)) STORED;
CREATE INDEX idx_artikel_carian ON artikel USING GIN (carian);
SELECT judul FROM artikel WHERE carian @@ to_tsquery('indonesian', 'buku & rak') ORDER BY judul;
DROP INDEX idx_artikel_carian;
ALTER TABLE artikel DROP COLUMN carian;
SQLALTER TABLE
CREATE INDEX
judul
------------------
Pemeliharaan rak
(1 row)
DROP INDEX
ALTER TABLEKolom terhitung menjamin vektornya selalu mutakhir tanpa trigger tambahan, dan konfigurasi bahasanya disebut eksplisit agar kolomnya sah sebagai ekspresi kolom terhitung. Operator & menyatakan DAN pada tsquery, sedangkan | menyatakan ATAU dan tanda seru menyatakan penolakan, sehingga syarat carian tersusun seperti logika boolean.
Peringkat hasil dengan ts_rank#
Hasil pencarian lazimnya disusun menurut relevansinya, dan ts_rank memberi skor berdasarkan kecocokan vektor terhadap carian. Skor mentahnya bermakna relatif dalam satu query, sehingga pengurutan turun menempatkan kecocokan terpadat di atas.
# peringkat kecocokan dengan ts_rank
psql -U postgres -d perpustakaan <<'SQL'
ALTER TABLE artikel ADD COLUMN carian TSVECTOR
GENERATED ALWAYS AS (to_tsvector('indonesian', isi)) STORED;
CREATE INDEX idx_artikel_carian ON artikel USING GIN (carian);
SELECT judul, ROUND(ts_rank(carian, to_tsquery('indonesian', 'buku | koleksi'))::NUMERIC, 4) AS skor
FROM artikel WHERE carian @@ to_tsquery('indonesian', 'buku | koleksi')
ORDER BY skor DESC, judul;
DROP INDEX idx_artikel_carian;
ALTER TABLE artikel DROP COLUMN carian;
DROP TABLE artikel;
SQLALTER TABLE
CREATE INDEX
judul | skor
-------------------+--------
Mengelola koleksi | 0.0608
Pemeliharaan rak | 0.0608
(2 rows)
DROP INDEX
ALTER TABLE
DROP TABLEDua artikel yang memuat kedua kata memperoleh skor lebih tinggi daripada yang memuat satu, dan pembulatan membuat skor terbaca pada laporan. Pemberat antara judul dan isi dilakukan dengan setweight pada vektor yang digabungkan, teknik lanjutan yang dirujuk pada dokumentasi kontrol pencarian, dan dasar peringkatnya tetap ts_rank yang sama.
Kesalahan umum#
Kesalahan pertama adalah memakai LIKE dengan awalan liar untuk pencarian kata, yang memindai seluruh tabel dan melewatkan variasi bentuk kata, padahal tsvector menyelesaikan keduanya. Kesalahan kedua adalah memanggil to_tsvector tanpa menyebut konfigurasi pada kolom terhitung, yang ditolak karena fungsinya tidak pasti (immutable) bila mengandalkan konfigurasi bawaan sesi.
Kesalahan ketiga adalah memperlakukan skor ts_rank sebagai angka mutlak yang dibandingkan antar query, padahal maknanya relatif dalam satu set hasil. Kesalahan keempat adalah lupa index GIN pada kolom vektor, sehingga setiap pencarian membangun vektor atas seluruh tabel, dan indexnya dibuat segera setelah kolomnya ada.
Istilah carian yang berasal dari kotak pencarian aplikasi tidak selalu berbentuk sintaks yang sah, dan to_tsquery menolak bentuk yang salah, sehingga dua pembungkusnya melayani masukan yang mentah. plainto_tsquery menyaring seluruh teksnya menjadi satu syarat DAN, sedangkan websearch_to_tsquery menerima bentuk pencarian web termasuk tanda kutip dan tanda hubung penolak. Pemilihan pembungkus ini menutup seluruh celah error istilah carian pada antarmuka pengguna.
Ringkasan#
- to_tsvector menormalkan teks menjadi leksimal bertanda posisi, dan @@ mencocokkannya dengan tsquery.
- Konfigurasi bahasa seperti indonesian menyatukan variasi bentuk kata tanpa daftar manual.
- Kolom tsvector terhitung yang diindeks GIN menjadikan pencarian dilayani index dengan vektor yang selalu mutakhir.
- ts_rank memberi skor kecocokan untuk pengurutan hasil, dengan makna yang relatif dalam satu query.
Referensi#
- Full Text Search — PostgreSQL Documentation — Bab induk pencarian teks penuh.
- Full Text Search Controls — PostgreSQL Documentation — tsvector, tsquery, dan pemberat hasil.
- Storing tsvector in the Database — PostgreSQL Documentation — Pola kolom terhitung dan index GIN.