Mode natural language adalah cara kerja MATCH ... AGAINST tanpa modifier apa pun — perilaku bawaan yang kita dapat begitu menulis query pencarian. Namanya sesuai perilakunya: server menerima kata kunci sebagaimana manusia mengetiknya, mengabaikan urutan kata, lalu menilai mirip tidaknya setiap baris terhadap kata kunci tersebut lewat skor relevansi. Pada chapter ini kita bedah perilaku mode ini pada data nyata: hasil yang muncul terurut relevansi, baris yang tidak relevan sama sekali tidak ditampilkan, angka skornya bisa kita tampilkan sebagai kolom, pencarian multi kata menjumlahkan kontribusi tiap kata, dan kata stopword diabaikan diam-diam sampai query kita mengembalikan hasil kosong tanpa pesan galat.

Menyiapkan Tabel Latihan #

Kita pakai kembali database belajar_fts dan tabel artikel dengan sepuluh artikel bertema teknologi, kopi, dan masakan, lengkap dengan index full-text multi kolom pada judul, isi:

sql
DROP DATABASE IF EXISTS belajar_fts;
CREATE DATABASE belajar_fts;
USE belajar_fts;

Output yang Dihasilkan:

text
Query OK, 0 rows affected, 1 warning (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Database changed
sql
CREATE 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.');

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Query OK, 10 rows affected (0.00 sec)
Records: 10  Duplicates: 0  Warnings: 0

Satu statement kecil sebelum mulai mencari. Pembaruan index full-text InnoDB diproses secara asinkron di latar belakang, sehingga statistik index sesaat setelah INSERT masih bisa berpindah-pindah — dan skor relevansi ikut bergeser dari satu eksekusi ke eksekusi lain. OPTIMIZE TABLE memaksa sinkronisasi index full-text tabel ini sampai tuntas, sehingga seluruh skor pada chapter ini stabil dan bisa direproduksi:

sql
OPTIMIZE TABLE artikel;

Output yang Dihasilkan:

text
+---------------------+----------+----------+-------------------------------------------------------------------+
| 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)

Baris note pada output bukan galat: InnoDB tidak punya operasi optimize khusus untuk tabel biasa, jadi server menggantinya dengan recreate + analyze — membangun ulang tabel lalu menyegarkan statistiknya — dan pekerjaan itulah sekaligus merapikan index full-text-nya.

Pencarian Kata Tunggal #

Bentuk paling sederhana dari natural language search mencari satu kata:

sql
SELECT id, judul FROM artikel WHERE MATCH(judul, isi) AGAINST ('rendang');

Output yang Dihasilkan:

text
+----+------------------------+
| id | judul                  |
+----+------------------------+
|  3 | Memasak Rendang Daging |
+----+------------------------+
1 row in set (0.00 sec)

Hanya artikel 3 yang memuat kata “rendang”, sehingga hasilnya satu baris. Sekarang kata yang lebih populer di koleksi kita:

sql
SELECT id, judul FROM artikel WHERE MATCH(judul, isi) AGAINST ('kopi');

Output yang Dihasilkan:

text
+----+---------------------------+
| id | judul                     |
+----+---------------------------+
|  5 | Asal Biji Kopi Gayo       |
|  2 | Panduan Menyeduh Espresso |
|  8 | Cold Brew vs Kopi Panas   |
+----+---------------------------+
3 rows in set (0.00 sec)

Tiga artikel tentang kopi muncul tanpa kita menulis ORDER BY — dan urutannya menarik: artikel 5 tampil lebih dulu, disusul artikel 2 dan 8. Dokumentasi MySQL menyebut perilaku ini secara eksplisit: ketika MATCH dipakai di klausa WHERE, baris dikembalikan otomatis terurut relevansi tertinggi lebih dulu, selama query tidak punya ORDER BY lain, pencarian memakai index full-text (bukan table scan), dan tidak digabung dengan tabel lain yang membuat full-text index scan tidak lagi menjadi tabel paling kiri. Urutan pada output di atas memang urutan relevansi — artikel 5 memuat kata “kopi” empat kali (sekali di judul, tiga kali di isi), satu kali lebih banyak daripada artikel 2 dan 8 yang masing-masing tiga kali, sehingga skornya paling tinggi.

Perilaku kedua yang tak kalah penting: baris yang sama sekali tidak memuat kata “kopi” tidak ikut tampil dengan skor nol. Mode natural language menyaring baris berskor 0 — tepatnya, WHERE MATCH ... AGAINST hanya mengembalikan baris dengan kemiripan di atas nol. Bandingkan dengan LIKE, yang tidak punya konsep “hampir cocok”: baris cocok atau gugur.

Menampilkan Skor Relevansi #

MATCH ... AGAINST bukan sekadar kondisi benar-salah — fungsi ini mengembalikan angka desimal. Kita bisa memanggilnya langsung di daftar SELECT:

sql
SELECT id, judul, MATCH(judul, isi) AGAINST ('kopi') AS skor
FROM artikel
ORDER BY skor DESC, id;

Output yang Dihasilkan:

text
+----+------------------------------+--------------------+
| id | judul                        | skor               |
+----+------------------------------+--------------------+
|  5 | Asal Biji Kopi Gayo          | 1.0936087369918823 |
|  2 | Panduan Menyeduh Espresso    | 0.8202065229415894 |
|  8 | Cold Brew vs Kopi Panas      | 0.8202065229415894 |
|  1 | Belajar Database Relasional  |                  0 |
|  3 | Memasak Rendang Daging       |                  0 |
|  4 | Cloud Computing untuk Pemula |                  0 |
|  6 | Nasi Goreng Kampung          |                  0 |
|  7 | JavaScript di Browser        |                  0 |
|  9 | Tempe Sumber Protein         |                  0 |
| 10 | Keamanan Aplikasi Web        |                  0 |
+----+------------------------------+--------------------+
10 rows in set (0.00 sec)

Sepuluh baris kini semuanya tampil — query ini tidak punya WHERE, sehingga fungsi MATCH hanya menghitung skor untuk setiap baris. Tiga baris teratas mendapat skor desimal yang jauh di atas nol, sisanya tepat 0. Nilai desimal panjang seperti 1.0936087369918823 adalah tampilan natural angka floating-point hasil perhitungan relevansi; kita bisa membulatkannya dengan ROUND(..., 4) bila tampilan laporan perlu dirapikan. Dua faktor utama yang menentukan besarnya skor: kata “kopi” muncul di seluruh teks artikel (judul digabung isi), dan kata itu langka pada koleksi — hanya 3 dari 10 artikel memuatnya, sehingga kemunculannya bernilai tinggi.

Mencari dengan Beberapa Kata #

Kata kunci bisa lebih dari satu, dipisah spasi:

sql
SELECT id, judul, MATCH(judul, isi) AGAINST ('kopi espresso') AS skor
FROM artikel
WHERE MATCH(judul, isi) AGAINST ('kopi espresso')
ORDER BY skor DESC, id;

Output yang Dihasilkan:

text
+----+---------------------------+--------------------+
| id | judul                     | skor               |
+----+---------------------------+--------------------+
|  2 | Panduan Menyeduh Espresso |  4.820206642150879 |
|  5 | Asal Biji Kopi Gayo       | 1.0936087369918823 |
|  8 | Cold Brew vs Kopi Panas   | 0.8202065229415894 |
+----+---------------------------+--------------------+
3 rows in set (0.00 sec)

Perilakunya meniru intuisi mesin pencari web: kata-kata diperlakukan opsional — baris yang memuat salah satu kata sudah lolos — tetapi baris yang memuat lebih banyak kata kunci mendapat skor lebih tinggi. Artikel 2 melonjak jauh ke posisi teratas karena memuat kata “espresso” berulang kali sekaligus kata “kopi”, sementara artikel 5 dan 8 yang hanya memuat “kopi” tetap di skor yang sama seperti pencarian satu kata. Kalimat WHERE MATCH ... AGAINST ('kopi espresso') dengan demikian setara secara semantik dengan “tampilkan baris yang berhubungan dengan kopi atau espresso, urutkan berdasarkan kepadatan kemunculannya”.

Kata yang Diam-diam Diabaikan #

Ada satu kejutan yang sering menjengkelkan pemula: kata pencarian bisa lenyap tanpa galat. Kata pertama yang lenyap adalah stopword — kata umum yang dibuang server sebelum mengindeks:

sql
SELECT id, judul FROM artikel WHERE MATCH(judul, isi) AGAINST ('the');

Output yang Dihasilkan:

text
Empty set (0.00 sec)

Hasilnya kosong — Empty set — padahal query berjalan tanpa pesan galat. Kata “the” ada di daftar stopword bawaan InnoDB, sehingga kata itu tidak pernah masuk index dan tidak pernah dicari. Daftar stopword bawaan itu berisi kata umum bahasa Inggris seperti “the”, “of”, dan “about”; kebetulan menguntungkan pengguna bahasa Indonesia karena hampir semua kata kita — “dan”, “yang”, “dengan” — tidak masuk daftar dan tetap terindeks penuh.

Kata kedua yang lenyap adalah kata yang lebih pendek dari innodb_ft_min_token_size — pada konfigurasi bawaan, tiga karakter:

sql
SELECT id, judul FROM artikel WHERE MATCH(judul, isi) AGAINST ('di');

Output yang Dihasilkan:

text
Empty set (0.00 sec)

Padahal kata “di” jelas ada di judul artikel 7 — “JavaScript di Browser” — dan tersebar di isi banyak artikel. Kata dua huruf dibuang saat tokenisasi karena lebih pendek dari panjang minimum token, jadi kata itu tidak pernah masuk index dan pencarian atasnya mengembalikan Empty set dengan senyap, persis seperti stopword. Sebagai pembanding positif, kata yang panjangnya memenuhi syarat dicari normal:

sql
SELECT id, judul FROM artikel WHERE MATCH(judul, isi) AGAINST ('javascript');

Output yang Dihasilkan:

text
+----+-----------------------+
| id | judul                 |
+----+-----------------------+
|  7 | JavaScript di Browser |
+----+-----------------------+
1 row in set (0.00 sec)

Kata “javascript” berada jauh di atas ambang tiga karakter sehingga terindeks penuh dan artikel 7 ditemukan. Perilaku senyap ini penting diketahui saat debugging: sebelum menuduh index rusak, periksa dulu apakah kata kunci kita stopword atau terlalu pendek.

Membersihkan Database Latihan #

Rangkaian latihan mode natural language selesai:

sql
DROP DATABASE belajar_fts;

Output yang Dihasilkan:

text
Query OK, 1 row affected (0.00 sec)

Referensi #

Mode natural language memberi pengalaman pencarian yang manusiawi: kata kunci bebas urutan, hasil terurut relevansi, dan skor bisa ditampilkan sebagai angka. Namun kadang kita butuh kendali presisi — “harus memuat kata ini, jangan tampilkan yang memuat kata itu, cari semua kata berawalan tertentu”. Kebutuhan itu dilayani mode IN BOOLEAN MODE beserta operator-operatornya, yang kita bahas di chapter Boolean Search.