Chapter sebelumnya menutup dengan satu fakta yang mengganjal: cast literal 'running'::tsvector @@ 'run'::tsquery menghasilkan false karena cast tidak menormalkan apa pun. Dokumen nyata berupa kalimat utuh, dan kueri nyata berupa teks yang diketik pengguna — keduanya harus melewati fungsi konversi agar jatuh ke lexeme yang sama. Fungsi itu adalah to_tsvector untuk dokumen dan to_tsquery untuk kueri, dan keduanya sama-sama menerima satu argumen tambahan yang menentukan nasib normalisasi: konfigurasi bahasa. Pada chapter ini kita membedah kerja kedua fungsi tersebut, memilih konfigurasi secara jujur untuk teks Indonesia, dan mengenal tiga fungsi pembantu yang mengubah teks polos menjadi kueri yang valid tanpa operator manual.

to_tsvector: Tokenisasi dan Normalisasi #

to_tsvector(konfigurasi, teks) menjalankan tiga tahap berurutan pada teks mentah. Parser memecah teks menjadi token, lalu kamus yang dipilih konfigurasi menormalkan setiap token — menyeragamkan huruf kecil, membuang stopword, dan mereduksi ke akar kata — dan hasilnya disimpan sebagai tsvector berikut posisi token aslinya. Perbandingan dua konfigurasi pada teks yang sama memperlihatkan betapa besar pengaruh pilihan konfigurasi:

sql to-tsvector.sql
-- 'english': stemming + pembuangan stopword. 'simple': hanya lowercase dan urut.
SELECT to_tsvector('english', 'The cats are running') AS konfigurasi_english,
       to_tsvector('simple', 'The cats are running') AS konfigurasi_simple;

-- Semua bentuk infleksi dari akar yang sama jatuh menjadi satu lexeme.
SELECT to_tsvector('english', 'cat cats run runs running') AS bentuk_kata_runtuh;

Output yang Dihasilkan:

text
 konfigurasi_english |          konfigurasi_simple          
---------------------+--------------------------------------
 'cat':2 'run':4     | 'are':3 'cats':2 'running':4 'the':1
(1 row)

  bentuk_kata_runtuh   
-----------------------
 'cat':1,2 'run':3,4,5
(1 row)

Dua hasil ini layak dibedah per baris:

  1. Baris pertama, kolom konfigurasi_english: 'cat':2 'run':4. Kata The dan are dibuang sebagai stopword bahasa Inggris, cats menjadi cat, dan running menjadi run. Posisi tetap mengacu pada token asli — cat berasal dari kata kedua, run dari kata keempat — sehingga informasi urutan kalimat tidak hilang meski kata di tengah dibuang.
  2. Baris pertama, kolom konfigurasi_simple: 'are':3 'cats':2 'running':4 'the':1. Konfigurasi simple hanya menyeragamkan huruf kecil lalu menyimpan setiap token apa adanya; tidak ada stopword yang dibuang dan tidak ada stemming. Untuk pencarian kata kunci yang presisi, perilaku seminim ini justru bisa menjadi nilai plus.
  3. Baris kedua: lima kata berbeda (cat, cats, run, runs, running) runtuh menjadi dua lexeme — 'cat':1,2 'run':3,4,5. Inilah mekanisme yang membuat pencarian satu kata mampu menangkap semua bentuk infleksinya sekaligus.

Konfigurasi Bahasa: simple, english, indonesian #

Konfigurasi adalah paket bernama yang berisi parser plus rangkaian kamus per jenis token. PostgreSQL 17 mengirimkan 29 konfigurasi siap pakai yang daftarnya bisa diperiksa langsung di catalog pg_ts_config:

sql konfigurasi.sql
-- Daftar konfigurasi yang terpasang bisa diperiksa di catalog pg_ts_config.
SELECT cfgname AS konfigurasi FROM pg_ts_config
WHERE cfgname IN ('simple', 'english', 'indonesian', 'malaysia')
ORDER BY cfgname;

-- 'indonesian' melakukan stemming bahasa Indonesia; 'simple' membiarkan kata utuh.
SELECT to_tsvector('indonesian', 'Kami sedang belajar PostgreSQL di kelas') AS konfigurasi_indonesian,
       to_tsvector('simple', 'Kami sedang belajar PostgreSQL di kelas') AS konfigurasi_simple;

-- Bentuk-bentuk kata berkerabat pada akar yang sama: semuanya menjadi 'ajar'.
SELECT to_tsvector('indonesian', 'belajar mengajar pelajaran') AS satu_akar;

-- Stemming Indonesia juga punya sisi kasar: kata tidak berkerabat ikut terpotong.
SELECT to_tsvector('indonesian', 'sekolah menekankan mengolah') AS potongan_berlebih;

Output yang Dihasilkan:

text
 konfigurasi 
-------------
 english
 indonesian
 simple
(3 rows)

                    konfigurasi_indonesian                    |                       konfigurasi_simple                        
--------------------------------------------------------------+-----------------------------------------------------------------
 'ajar':3 'di':5 'kami':1 'kelas':6 'postgresql':4 'sedang':2 | 'belajar':3 'di':5 'kami':1 'kelas':6 'postgresql':4 'sedang':2
(1 row)

  satu_akar   
--------------
 'ajar':1,2,3
(1 row)

      potongan_berlebih      
-----------------------------
 'ekan':2 'mengo':3 'seko':1
(1 row)

Hasilnya memberi dasar pemilihan yang jujur untuk korpus kita:

  1. Baris pertama: dari empat nama yang diuji, tiga benar-benar terpasang — english, indonesian, dan simple — sementara malaysia tidak ada. PostgreSQL tidak menebak-nebak nama konfigurasi; nama yang tidak dikenal langsung ditolak (kita lihat error-nya sebentar lagi).
  2. Baris kedua: konfigurasi indonesian mereduksi belajar menjadi ajar, sementara simple menyimpan belajar utuh. Perhatikan juga bahwa di, kami, dan sedang tetap disimpan oleh indonesian — PostgreSQL mengirimkan stemmer bahasa Indonesia, tetapi tanpa daftar stopword bahasa Indonesia, sehingga kata-kata umum itu ikut tersimpan sebagai lexeme.
  3. Baris ketiga: belajar, mengajar, dan pelajaran runtuh menjadi satu lexeme 'ajar':1,2,3 — janji utama full-text search untuk bahasa Indonesia.
  4. Baris keempat: sisi kasarnya. sekolah menjadi seko, menekankan menjadi ekan, dan mengolah menjadi mengo. Stemmer bahasa Indonesia bawaan kadang memotong berlebihan sehingga kata yang tidak berkerabat bisa berakhir pada lexeme yang sama.

Untuk korpus artikel kita — bahasa Indonesia yang dicampur istilah Inggris — chapter berikutnya akan konsisten memakai konfigurasi simple: tanpa stemming, perilakunya mudah ditebak, dan kata campuran seperti JSONB atau running tersimpan apa adanya. Konfigurasi indonesian layak diuji kalau recall bentuk kata lebih penting daripada presisi, dan pembahasan cara mengendalikan konfigurasi secara lebih dalam — termasuk stopword sendiri — menempati chapter Konfigurasi Bahasa.

Mencoba konfigurasi yang tidak ada tidak berakhir diam-diam, melainkan dengan error yang menyebut nama konfigurasi yang dicari:

-- Nama konfigurasi yang tidak ada ditolak dengan error.
SELECT to_tsvector('malaysia', 'selamat datang');

Output yang Dihasilkan:

text
psql:konfigurasi-error.sql:2: ERROR:  text search configuration "malaysia" does not exist
LINE 1: SELECT to_tsvector('malaysia', 'selamat datang');
                           ^

to_tsquery: Kueri dengan Operator Manual #

to_tsquery(konfigurasi, teks) menormalkan setiap token input dengan konfigurasi yang sama seperti to_tsvector, lalu merangkainya mengikuti operator yang Anda tulis sendiri:

sql to-tsquery.sql
-- Sisi kueri ikut dinormalkan: "running" dan "cats" menjadi lexeme akarnya.
SELECT to_tsquery('english', 'running') AS satu_kata,
       to_tsquery('english', 'cats | running') AS dua_kata;

-- Operator AND, OR, dan NOT tetap ditulis manual di input.
SELECT to_tsquery('english', 'cat & !dog') AS logika;

-- Operator frasa dan jarak juga dikenal oleh to_tsquery.
SELECT to_tsquery('simple', 'belajar <-> php') AS frasa,
       to_tsquery('simple', 'belajar <2> php') AS jarak;

Output yang Dihasilkan:

text
 satu_kata |   dua_kata    
-----------+---------------
 'run'     | 'cat' | 'run'
(1 row)

     logika     
----------------
 'cat' & !'dog'
(1 row)

        frasa        |        jarak        
---------------------+---------------------
 'belajar' <-> 'php' | 'belajar' <2> 'php'
(1 row)

Perhatikan kolom dua_kata: input cats | running keluar sebagai 'cat' | 'run' — normalisasi berjalan dulu, operator merangkai hasilnya. Karena operator wajib ditulis manual, teks pengguna yang polos langsung ditolak parser:

-- to_tsquery menuntut operator di antara setiap token.
SELECT to_tsquery('simple', 'belajar php');

Output yang Dihasilkan:

text
psql:to-tsquery-error.sql:2: ERROR:  syntax error in tsquery: "belajar php"

Error yang sama dengan chapter sebelumnya — dan inilah alasan tiga fungsi pembantu berikutnya ada.

Tiga Pembantu untuk Teks Polos #

FungsiInput yang diterimaHubungan antar kataKekhususan
to_tsquerytoken + operator manualsesuai operator yang dituliskendali penuh, teks polos ditolak
plainto_tsqueryteks polossemua kata di-AND-kanpaling toleran terhadap simbol
phraseto_tsqueryteks poloskata dirangkai sebagai frasa berurutanurutan dan jarak dihitung
websearch_to_tsquerygaya kotak pencariankata di-AND-kanmendukung "frasa", OR, dan -pengecualian

Kita uji ketiganya pada input yang sama supaya perbedaannya terlihat bersebelahan:

-- plainto_tsquery: semua kata di-AND-kan, karakter operator diabaikan.
SELECT plainto_tsquery('simple', 'belajar php') AS plain_biasa,
       plainto_tsquery('simple', 'belajar & php') AS plain_simbol;

-- phraseto_tsquery: kata dirangkai sebagai frasa berurutan.
SELECT phraseto_tsquery('simple', 'belajar php pemula') AS frasa_panjang;

-- Stopword yang dibuang tetap dihitung sebagai jarak antar kata.
SELECT phraseto_tsquery('english', 'The cat and the rats') AS frasa_stopword;

-- websearch_to_tsquery: sintaks ala kotak pencarian mesin pencari.
SELECT websearch_to_tsquery('simple', 'belajar php') AS kata_biasa,
       websearch_to_tsquery('simple', '"belajar php"') AS frasa_kutip,
       websearch_to_tsquery('simple', 'belajar OR php') AS operator_atau,
       websearch_to_tsquery('simple', 'belajar -php') AS pengecualian;

Output yang Dihasilkan:

text
    plain_biasa    |   plain_simbol    
-------------------+-------------------
 'belajar' & 'php' | 'belajar' & 'php'
(1 row)

          frasa_panjang           
----------------------------------
 'belajar' <-> 'php' <-> 'pemula'
(1 row)

 frasa_stopword  
-----------------
 'cat' <3> 'rat'
(1 row)

    kata_biasa     |     frasa_kutip     |   operator_atau   |    pengecualian    
-------------------+---------------------+-------------------+--------------------
 'belajar' & 'php' | 'belajar' <-> 'php' | 'belajar' | 'php' | 'belajar' & !'php'
(1 row)

Empat baris hasil ini merangkum karakter masing-masing fungsi:

  1. plain_biasa dan plain_simbol: keduanya menghasilkan 'belajar' & 'php'. plainto_tsquery memecah input berdasarkan katanya; simbol & pada input tidak dibaca sebagai operator, jadi teks pengguna yang kebetulan berisi karakter istimewa tidak merusak kueri.
  2. frasa_panjang: tiga kata dirangkai 'belajar' <-> 'php' <-> 'pemula'. Frasa hanya cocok bila ketiga kata muncul berurutan persis seperti ditulis.
  3. frasa_stopword: 'cat' <3> 'rat'. Kata the dan and dibuang dari kueri, tetapi posisinya tetap dihitung sebagai jarak — rat memang berjarak tepat tiga posisi dari cat dalam kalimat aslinya. Tanpa kehati-hatian ini, frasa yang dipisah stopword tidak akan pernah cocok.
  4. Baris websearch: satu fungsi, empat perilaku. Kata biasa di-AND-kan, kutip ganda menjadi frasa, OR menjadi alternatif, dan tanda minus menjadi pengecualian — persis kebiasaan pengguna mesin pencari.

Dokumen dan kueri kini sama-sama bisa dibangun dari teks mentah. Chapter berikutnya, Pencarian Teks, merangkai semuanya menjadi alur kerja lengkap pada tabel artikel: pencarian di kolom gabungan, pengurutan hasil berdasarkan skor ts_rank, cuplikan hasil dengan ts_headline, hingga pola kolom tsvector yang tersimpan otomatis.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

konfigurasi-error.sql

Unduh berkas
sql
-- Nama konfigurasi yang tidak ada ditolak dengan error.
SELECT to_tsvector('malaysia', 'selamat datang');

konfigurasi.sql

Unduh berkas
sql
-- Daftar konfigurasi yang terpasang bisa diperiksa di catalog pg_ts_config.
SELECT cfgname AS konfigurasi FROM pg_ts_config
WHERE cfgname IN ('simple', 'english', 'indonesian', 'malaysia')
ORDER BY cfgname;

-- 'indonesian' melakukan stemming bahasa Indonesia; 'simple' membiarkan kata utuh.
SELECT to_tsvector('indonesian', 'Kami sedang belajar PostgreSQL di kelas') AS konfigurasi_indonesian,
       to_tsvector('simple', 'Kami sedang belajar PostgreSQL di kelas') AS konfigurasi_simple;

-- Bentuk-bentuk kata berkerabat pada akar yang sama: semuanya menjadi 'ajar'.
SELECT to_tsvector('indonesian', 'belajar mengajar pelajaran') AS satu_akar;

-- Stemming Indonesia juga punya sisi kasar: kata tidak berkerabat ikut terpotong.
SELECT to_tsvector('indonesian', 'sekolah menekankan mengolah') AS potongan_berlebih;

query-pembantu.sql

Unduh berkas
sql
-- plainto_tsquery: semua kata di-AND-kan, karakter operator diabaikan.
SELECT plainto_tsquery('simple', 'belajar php') AS plain_biasa,
       plainto_tsquery('simple', 'belajar & php') AS plain_simbol;

-- phraseto_tsquery: kata dirangkai sebagai frasa berurutan.
SELECT phraseto_tsquery('simple', 'belajar php pemula') AS frasa_panjang;

-- Stopword yang dibuang tetap dihitung sebagai jarak antar kata.
SELECT phraseto_tsquery('english', 'The cat and the rats') AS frasa_stopword;

-- websearch_to_tsquery: sintaks ala kotak pencarian mesin pencari.
SELECT websearch_to_tsquery('simple', 'belajar php') AS kata_biasa,
       websearch_to_tsquery('simple', '"belajar php"') AS frasa_kutip,
       websearch_to_tsquery('simple', 'belajar OR php') AS operator_atau,
       websearch_to_tsquery('simple', 'belajar -php') AS pengecualian;

to-tsquery-error.sql

Unduh berkas
sql
-- to_tsquery menuntut operator di antara setiap token.
SELECT to_tsquery('simple', 'belajar php');

to-tsquery.sql

Unduh berkas
sql
-- Sisi kueri ikut dinormalkan: "running" dan "cats" menjadi lexeme akarnya.
SELECT to_tsquery('english', 'running') AS satu_kata,
       to_tsquery('english', 'cats | running') AS dua_kata;

-- Operator AND, OR, dan NOT tetap ditulis manual di input.
SELECT to_tsquery('english', 'cat & !dog') AS logika;

-- Operator frasa dan jarak juga dikenal oleh to_tsquery.
SELECT to_tsquery('simple', 'belajar <-> php') AS frasa,
       to_tsquery('simple', 'belajar <2> php') AS jarak;

to-tsvector.sql

Unduh berkas
sql
-- 'english': stemming + pembuangan stopword. 'simple': hanya lowercase dan urut.
SELECT to_tsvector('english', 'The cats are running') AS konfigurasi_english,
       to_tsvector('simple', 'The cats are running') AS konfigurasi_simple;

-- Semua bentuk infleksi dari akar yang sama jatuh menjadi satu lexeme.
SELECT to_tsvector('english', 'cat cats run runs running') AS bentuk_kata_runtuh;