Mode natural language memperlakukan kata kunci sebagai keinginan yang longgar: semua kata opsional, hasil diurutkan relevansi. Sering kali kita justru butuh aturan keras — " artikel wajib memuat kata ini, dilarang memuat kata itu, dan kata pencariannya boleh berawalan apa saja". Mode IN BOOLEAN MODE menghadirkan kendali itu lewat operator yang ditulis langsung di dalam string pencarian: + menjadikan kata wajib, - melarangnya, * mencari semua kata berawalan tertentu, kutip dua memaksa frasa persis, dan tanda kurung mengelompokkan syarat. Pada chapter ini kita uji setiap operator pada data artikel yang sama, melihat tabel operator lengkap beserta catatan InnoDB, dan membuktikan kenyataan yang sering disalahpahami: skor relevansi justru tetap dihitung di mode boolean.
Menyiapkan Tabel Latihan #
Tabel latihan tetap artikel dengan sepuluh artikel bertema teknologi, kopi, dan masakan, plus index full-text multi kolom idx_ft_artikel dan OPTIMIZE TABLE agar statistik index stabil:
DROP DATABASE IF EXISTS belajar_fts;
CREATE DATABASE belajar_fts;
USE belajar_fts;Output yang Dihasilkan:
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Database changedCREATE TABLE artikel (
id INT AUTO_INCREMENT PRIMARY KEY,
judul VARCHAR(100) NOT NULL,
isi TEXT NOT NULL,
FULLTEXT idx_ft_artikel (judul, isi)
) ENGINE=InnoDB;
INSERT INTO artikel (judul, isi) VALUES
('Belajar Database Relasional', 'Database relasional menyimpan data dalam tabel yang saling terhubung. MySQL adalah database relasional populer untuk aplikasi web. Indeks mempercepat pencarian data pada kolom tertentu.'),
('Panduan Menyeduh Espresso', 'Espresso adalah basis minuman kopi modern. Seduh espresso memakai mesin kopi bertekanan tinggi dan biji kopi gelap. Grinder yang baik menghasilkan rasa espresso seimbang.'),
('Memasak Rendang Daging', 'Rendang daging adalah masakan khas Padang. Memasak rendang membutuhkan santan, cabai, dan rempah. Proses memasak rendang memakan waktu beberapa jam hingga kuah mengering.'),
('Cloud Computing untuk Pemula', 'Cloud computing menyediakan server dan penyimpanan lewat internet. Perusahaan menyewa cloud agar tidak membeli server fisik. Layanan cloud populer contohnya penyimpanan berkas dan basis data terkelola.'),
('Asal Biji Kopi Gayo', 'Kopi Gayo tumbuh di dataran tinggi Aceh. Petani kopi Gayo memanen biji secara manual. Citarasa kopi Gayo khas dengan aroma rempah dan keasaman seimbang.'),
('Nasi Goreng Kampung', 'Nasi goreng kampung adalah masakan rumahan favorit. Bumbu nasi goreng terdiri dari bawang, cabai, dan kecap. Memasak nasi goreng butuh api besar agar wangi.'),
('JavaScript di Browser', 'JavaScript membuat halaman web menjadi interaktif. Browser menjalankan JavaScript pada sisi klien. Framework JavaScript mempercepat pembuatan antarmuka aplikasi modern.'),
('Cold Brew vs Kopi Panas', 'Cold brew diseduh dengan air dingin selama belasan jam. Rasa cold brew lebih halus dibanding kopi panas. Penikmat kopi sering menyimpan cold brew di kulkas.'),
('Tempe Sumber Protein', 'Tempe adalah makanan fermentasi kedelai khas Indonesia. Tempe mengandung protein tinggi dan harganya murah. Olahan tempe contohnya tempe goreng, tempe bacem, dan tempe crispy.'),
('Keamanan Aplikasi Web', 'Keamanan aplikasi web wajib dijaga sejak awal pengembangan. Pengembang memvalidasi input pengguna dan menyimpan sandi terenkripsi. Serangan umum pada aplikasi web contohnya SQL injection.');
OPTIMIZE TABLE artikel;Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 10 rows affected (0.00 sec)
Records: 10 Duplicates: 0 Warnings: 0
+---------------------+----------+----------+-------------------------------------------------------------------+
| Table | Op | Msg_type | Msg_text |
+---------------------+----------+----------+-------------------------------------------------------------------+
| belajar_fts.artikel | optimize | note | Table does not support optimize, doing recreate + analyze instead |
| belajar_fts.artikel | optimize | status | OK |
+---------------------+----------+----------+-------------------------------------------------------------------+
2 rows in set (0.00 sec)Kata Wajib dan Kata Terlarang #
Dua operator paling sering dipakai adalah + di depan kata yang wajib ada dan - di depan kata yang dilarang ada. Query berikut mencari artikel yang wajib memuat “kopi”:
SELECT id, judul FROM artikel WHERE MATCH(judul, isi) AGAINST ('+kopi' IN BOOLEAN MODE);Output yang Dihasilkan:
+----+---------------------------+
| id | judul |
+----+---------------------------+
| 5 | Asal Biji Kopi Gayo |
| 2 | Panduan Menyeduh Espresso |
| 8 | Cold Brew vs Kopi Panas |
+----+---------------------------+
3 rows in set (0.00 sec)Hasilnya sama tiga artikel seperti mode natural language — pada data sekecil ini +kopi memang identik dengan pencarian biasa. Perbedaannya baru terasa saat operator digabungkan. Sekarang kita tambahkan larangan: wajib memuat “kopi”, dilarang memuat “espresso”:
SELECT id, judul FROM artikel WHERE MATCH(judul, isi) AGAINST ('+kopi -espresso' IN BOOLEAN MODE);Output yang Dihasilkan:
+----+-------------------------+
| id | judul |
+----+-------------------------+
| 5 | Asal Biji Kopi Gayo |
| 8 | Cold Brew vs Kopi Panas |
+----+-------------------------+
2 rows in set (0.00 sec)Artikel 2 hilang karena memuat kata “espresso” — padahal artikel itu paling banyak menyebut “kopi”. Begitulah cara kerja -: baris gugur total, tidak peduli seberapa relevan kata-kata lainnya. Kebalikannya juga penting diketahui: operator - hanya berfungsi mengecualikan baris yang sudah dicocokkan oleh syarat lain. Query yang isinya hanya kata terlarang tidak mengembalikan “semua baris selain yang terlarang”, melainkan hasil kosong:
SELECT id, judul FROM artikel WHERE MATCH(judul, isi) AGAINST ('-espresso' IN BOOLEAN MODE);Output yang Dihasilkan:
Empty set (0.00 sec)Tanpa satu pun kata positif, tidak ada baris yang cocok untuk kemudian dikecualikan — hasilnya Empty set. Pola mentalnya seperti kalimat “tampilkan yang memuat X, tapi bukan yang memuat Y”: bagian “memuat X” wajib ada lebih dulu.
Tabel Operator Lengkap #
String pencarian boolean mendukung operator berikut — tanda operator ditulis menempel di kata yang dimaksud:
| Operator | Fungsi | Contoh |
|---|---|---|
+kata | Kata wajib ada di setiap baris hasil | '+kopi -espresso' |
-kata | Kata dilarang ada di baris hasil | '+kopi -espresso' |
kata (tanpa operator) | Kata opsional; kehadirannya menaikkan skor | 'kopi espresso' |
kata* | Truncation: semua kata berawalan tersebut | 'mem*' |
"frasa" | Frasa persis — kata sama dalam urutan sama | '"nasi goreng"' |
( ) | Pengelompokan subekspresi | '+masakan (rendang nasi)' |
>kata | Naikkan kontribusi kata pada skor | '+kopi >espresso' |
<kata | Turunkan kontribusi kata pada skor | '+kopi <espresso' |
~kata | Kontribusi negatif — menurunkan skor, tidak menggugurkan | '+kopi ~espresso' |
"a b" @N | Kedekatan: kata-kata berjarak maksimum N kata | '"kopi aceh"@2' |
Beberapa catatan penting khusus InnoDB. Pertama, tanda + dan - hanya boleh di awal kata: 'kopi+' atau '+-kata' ditolak sebagai galat sintaks. Kedua, satu kata tidak boleh memakai dua operator sekaligus seperti '++kopi'. Ketiga, operator @jarak hanya bermakna pada frasa berkutip dan hasil pengukurannya di InnoDB bisa tidak intuitif — uji selalu di data Anda sendiri sebelum mengandalkannya.
Truncation: Mencari Semua Awalan Kata #
Operator * di akhir kata mencocokkan setiap kata berawalan sama — perluasan “mulai dengan” milik LIKE 'awalan%', tetapi tetap memakai index terbalik sehingga cepat pada data besar. Awalan mem* menangkap semua kata kerja berawalan “mem” di koleksi kita:
SELECT id, judul FROM artikel WHERE MATCH(judul, isi) AGAINST ('mem*' IN BOOLEAN MODE) ORDER BY id;Output yang Dihasilkan:
+----+------------------------------+
| id | judul |
+----+------------------------------+
| 1 | Belajar Database Relasional |
| 2 | Panduan Menyeduh Espresso |
| 3 | Memasak Rendang Daging |
| 4 | Cloud Computing untuk Pemula |
| 5 | Asal Biji Kopi Gayo |
| 6 | Nasi Goreng Kampung |
| 7 | JavaScript di Browser |
| 10 | Keamanan Aplikasi Web |
+----+------------------------------+
8 rows in set (0.00 sec)Delapan dari sepuluh artikel memuat minimal satu kata berawalan “mem” — memasak, memakai, membeli, memanen, mempercepat, memvalidasi, dan kawan-kawannya. Dua artikel yang lolos pun informatif: artikel 8 tidak memuat satu kata berawalan “mem” pun (kata menyimpan-nya berawalan “men”), dan artikel 9 hanya memuat makanan (awalan “mak”). Operator truncation memang perlu awalan yang benar-benar spesifik; men* akan menangkap jauh lebih banyak artikel karena prefiks itu lebih umum dalam bahasa Indonesia. Satu keistimewaan *: kata yang membawanya tidak dibuang meskipun lebih pendek dari innodb_ft_min_token_size atau termasuk stopword — 'ke*' tetap dicari sebagai awalan, bukan dibuang seperti kata “ke” polos.
Frasa Persis dengan Kutip Dua #
Mencari dua kata tanpa kutip berarti dua kata opsional yang masing-masing bebas berada di mana saja. Kutip dua mengubah makna menjadi frasa: kata-kata harus muncul berdampingan dalam urutan yang sama:
SELECT id, judul FROM artikel WHERE MATCH(judul, isi) AGAINST ('nasi goreng' IN BOOLEAN MODE);Output yang Dihasilkan:
+----+----------------------+
| id | judul |
+----+----------------------+
| 6 | Nasi Goreng Kampung |
| 9 | Tempe Sumber Protein |
+----+----------------------+
2 rows in set (0.00 sec)SELECT id, judul FROM artikel WHERE MATCH(judul, isi) AGAINST ('"nasi goreng"' IN BOOLEAN MODE);Output yang Dihasilkan:
+----+---------------------+
| id | judul |
+----+---------------------+
| 6 | Nasi Goreng Kampung |
+----+---------------------+
1 row in set (0.00 sec)Tanpa kutip, artikel 9 ikut tampil karena memuat kata “goreng” di frasa tempe goreng — padahal artikel itu tidak membahas nasi goreng sama sekali. Dengan kutip, hanya artikel 6 yang memuat frasa “nasi goreng” secara berdampingan. Perhatikan pula bahwa frasa dicocokkan berdasarkan urutan kata, bukan karakter persisnya — tanda baca di antara kata diabaikan. Frasa yang seluruh katanya tidak terindeks — karena stopword atau terlalu pendek — menghasilkan Empty set tanpa galat.
Pengelompokan dengan Kurung #
Tanda kurung membentuk subekspresi, berguna menggabungkan wajib dengan pilihan:
SELECT id, judul FROM artikel WHERE MATCH(judul, isi) AGAINST ('+masakan (rendang nasi)' IN BOOLEAN MODE) ORDER BY id;Output yang Dihasilkan:
+----+------------------------+
| id | judul |
+----+------------------------+
| 3 | Memasak Rendang Daging |
| 6 | Nasi Goreng Kampung |
+----+------------------------+
2 rows in set (0.00 sec)Query ini terbaca: “wajib memuat masakan, DAN memuat rendang ATAU nasi”. Artikel 3 lolos lewat kata masakan dan rendang; artikel 6 lewat masakan dan nasi. Tanpa kurung, +masakan rendang nasi akan memaksa hanya “masakan” yang wajib sementara dua kata lainnya sekadar menaikkan skor — hasilnya bisa jauh lebih banyak. Kurung bisa bersarang seperti kurung matematika untuk logika yang lebih rumit, misalnya '+masakan +(rendang -daging)'.
Skor Relevansi Tetap Dihitung #
Anggapan yang beredar di banyak tutorial — “mode boolean tidak menghitung relevansi” — tidak sesuai dengan perilaku nyata MySQL 8.4 yang kita uji. Saat MATCH ... AGAINST mode boolean dipanggil di daftar SELECT, server tetap mengembalikan angka skor untuk setiap baris:
SELECT id, judul, MATCH(judul, isi) AGAINST ('+kopi' IN BOOLEAN MODE) AS skor
FROM artikel
ORDER BY id;Output yang Dihasilkan:
+----+------------------------------+--------------------+
| id | judul | skor |
+----+------------------------------+--------------------+
| 1 | Belajar Database Relasional | 0 |
| 2 | Panduan Menyeduh Espresso | 0.8202065229415894 |
| 3 | Memasak Rendang Daging | 0 |
| 4 | Cloud Computing untuk Pemula | 0 |
| 5 | Asal Biji Kopi Gayo | 1.0936087369918823 |
| 6 | Nasi Goreng Kampung | 0 |
| 7 | JavaScript di Browser | 0 |
| 8 | Cold Brew vs Kopi Panas | 0.8202065229415894 |
| 9 | Tempe Sumber Protein | 0 |
| 10 | Keamanan Aplikasi Web | 0 |
+----+------------------------------+--------------------+
10 rows in set (0.00 sec)Sepuluh baris tampil lengkap dengan skor — tiga artikel kopi membawa skor seperti pencarian natural language, sisanya nol. Yang memang tidak dijanjikan adalah pengurutan otomatis: dokumentasi MySQL menyatakan hasil boolean mode tidak dijamin terurut menurun relevansi, jadi tanggung jawab urutan ada pada ORDER BY kita — persis seperti kebiasaan yang sudah kita pakai sepanjang section ini. Kombinasi ORDER BY skor DESC dengan alias di daftar SELECT adalah pola yang rapi untuk halaman hasil pencarian mode boolean.
Kata Pendek dan Stopword di Boolean Mode #
Aturan token minimum dan stopword tetap berlaku di mode boolean — dengan satu pengecualian menarik. Kata dua huruf tetap tidak bisa dicari:
SELECT id, judul FROM artikel WHERE MATCH(judul, isi) AGAINST ('+di' IN BOOLEAN MODE);Output yang Dihasilkan:
Empty set (0.00 sec)Kata “di” lenyap saat tokenisasi sehingga query berisi +di saja setara query kosong — Empty set tanpa pesan galat. Pengecualiannya sudah kita singgung di bagian truncation: kata yang membawa operator * tidak dibuang oleh filter panjang minimum maupun stopword, sehingga 'di*' akan tetap mencari semua kata berawalan “di”. Solusi lain untuk kata pendek adalah keluar dari full-text search sama sekali — pencocokan LIKE dengan index B-tree tetap menangani kolom pendek dengan lebih tepat.
Membersihkan Database Latihan #
Rangkaian latihan operator selesai:
DROP DATABASE belajar_fts;Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)Referensi #
- Boolean Full-Text Searches — MySQL 8.4 Reference Manual
- Full-Text Search Functions — MySQL 8.4 Reference Manual
Operator boolean memberi kita kendali penuh atas syarat pencarian — wajib, terlarang, awalan, frasa, hingga pengelompokan — lengkap dengan skor yang tetap terhitung. Ada satu trik terakhir milik MATCH ... AGAINST yang bekerja di atas hasil pencarian: mode query expansion yang mencari dua kali dan memakai kata-kata hasil pencarian pertama untuk memperluas pencarian kedua. Perilakunya mengejutkan — dari satu hasil bisa melebar ke seluruh koleksi — dan kita bedah di chapter Query Expansion.