Binding Adalah Cara Nilai Masuk ke Prepared Statement
Prepared statement adalah SQL yang punya lubang. Binding adalah tindakan mengisi lubang itu dengan nilai: dengan aman, satu per satu, lewat API driver, bukan dengan menempelkan string begitu saja.
Bentuknya selalu sama: tulis SQL dengan placeholder, lalu serahkan nilainya secara terpisah.
Di CLI, kamu tidak bisa benar-benar mendemonstrasikan binding (shell tidak terhubung ke kode aplikasi), tapi SQL di atas persis seperti yang dikirim aplikasimu. Tanda ? adalah placeholder. Driver-mu, misalnya sqlite3 di Python, better-sqlite3 di Node, atau rusqlite di Rust, mengisinya lewat pemanggilan bind yang terpisah.
Gambaran sederhananya: SQL adalah resep, nilai yang di-bind adalah bahannya. Keduanya tidak pernah bercampur.
Placeholder Posisional: ?
Placeholder paling sederhana adalah ?. Masing-masing cocok dengan nilai berikutnya yang kamu bind, sesuai urutan.
INSERT INTO users (name, email) VALUES (?, ?);
Di Python, bentuknya:
cursor.execute(
"INSERT INTO users (name, email) VALUES (?, ?)",
("Rosa", "rosa@example.com"),
)
? pertama mendapat "Rosa", yang kedua mendapat "rosa@example.com". Kalau kamu memberikan nilai yang terlalu sedikit atau terlalu banyak, driver akan memunculkan error sebelum pernyataan dijalankan.
Kamu juga bisa memberi nomor secara eksplisit dengan ?1, ?2, ?3. Ini berguna kalau nilai yang sama muncul lebih dari sekali:
SELECT ?1 AS greeting, ?1 AS still_the_same;
?1 memakai ulang nilai pertama yang di-bind. Tanpa nomor, kamu harus bind nilai yang sama dua kali.
Placeholder Bernama: :name
Begitu sebuah pernyataan punya lebih dari dua atau tiga lubang, binding posisional berubah jadi permainan tebak-tebakan. Parameter bernama mengatasinya:
INSERT INTO users (name, email)
VALUES (:name, :email);
Di Python:
cursor.execute(
"INSERT INTO users (name, email) VALUES (:name, :email)",
{"name": "Boris", "email": "boris@example.com"},
)
Urutan key di dictionary tidak penting, hanya namanya yang penting. SQLite juga menerima @name dan $name sebagai prefiks alternatif, dan semuanya berperilaku sama. :name sejauh ini yang paling umum.
Parameter bernama langsung terasa manfaatnya saat kamu punya UPDATE dengan lima kolom, atau query yang memakai nilai yang sama di WHERE dan RETURNING.
Binding NULL
Cara yang benar untuk memasukkan NULL adalah mengirim nilai null dari bahasamu lewat API binding. Driver yang menangani penerjemahannya:
INSERT INTO users (name, email) VALUES (?, ?);
-- Bind: ("Cyrus", None) in Python
-- Bind: ["Cyrus", null] in Node
SELECT id, name, email FROM users;
None, null, nil, apa pun sebutannya di bahasamu, driver mengubahnya menjadi NULL SQL yang sebenarnya. Jangan bind string "NULL", karena itu menyimpan teks empat karakter "NULL". Dan jangan sisipkan kata NULL ke teks SQL, karena itu sama sekali menghilangkan manfaat binding.
Aturan yang sama berlaku untuk angka, blob, dan tanggal: kirim nilai aslinya, biarkan driver yang melakukan bind.
Memakai Ulang Pernyataan dengan Nilai Berbeda
Binding sangat cocok dipasangkan dengan prepared statement. Prepare sekali, lalu bind dan eksekusi berkali-kali. Parser bekerja satu kali saja dan database memakai ulang rencana yang sudah dikompilasi untuk setiap kumpulan nilai yang di-bind.
INSERT INTO users (name, email) VALUES (?, ?);
-- Bind ("Ada", "ada@example.com") -> execute
-- Bind ("Boris", "boris@example.com") -> execute
-- Bind ("Cyrus", NULL) -> execute
SELECT id, name, email FROM users ORDER BY id;
Sebagian besar driver membungkus pola ini dalam executemany (Python) atau perulangan .run() (Node). Apa pun caranya, yang kamu hemat adalah biaya parsing, yang kecil untuk setiap pernyataan tapi terasa saat kamu memasukkan ribuan baris.
Jangan Campur Gaya dalam Satu Pernyataan
Secara teknis, SQLite mengizinkan placeholder posisional dan bernama dalam pernyataan yang sama. Tahan godaan itu.
-- Legal but a footgun:
INSERT INTO users (name, email) VALUES (?, :email);
Pembaca harus melacak dua API binding sekaligus di kepalanya, dan sebagian besar driver tidak mendukung bentuk campuran dengan rapi. Pilih satu gaya per pernyataan: ? untuk satu atau dua nilai, :name untuk selebihnya.
Kesalahan Umum: Binding Bukan Format String
Inti dari binding adalah nilai tidak melewati parsing SQL. Bandingkan dua baris Python ini:
# Wrong - string formatting:
cursor.execute(f"SELECT * FROM users WHERE name = '{name}'")
# Right - parameter binding:
cursor.execute("SELECT * FROM users WHERE name = ?", (name,))
Baris pertama membangun SQL dengan penyambungan string. Kalau name berisi "'; DROP TABLE users; --", database dengan senang hati mem-parse dan menjalankan pernyataan sisipan itu. Baris kedua mengirim SQL dan nilai lewat jalur yang berbeda: nilai di-bind sebagai string, titik, apa pun karakter di dalamnya. Itulah sebabnya setiap panduan menyuruhmu memakai binding: ini bukan soal gaya, melainkan soal apa yang dilihat parser.
Kita akan membahas sisi injection dari hal ini di halaman berikutnya.
Kesalahan Lain: Kamu Tidak Bisa Bind Identifier
Placeholder bekerja untuk nilai: string, angka, blob, NULL. Placeholder tidak bekerja untuk nama tabel, nama kolom, atau kata kunci SQL:
-- This does NOT do what you want:
SELECT * FROM ? WHERE id = ?;
-- The first ? binds as a string literal, not a table name.
Kalau kamu benar-benar butuh nama tabel atau kolom yang dinamis (jarang terjadi di kode aplikasi), validasi nama itu terhadap daftar yang diizinkan lalu sambungkan sendiri ke SQL, jangan pernah langsung dari input pengguna. Untuk selebihnya, pakai binding.
Contoh Lengkap
Kalau semua bagian digabungkan, berikut tabel users kecil yang ditulis dan dibaca sepenuhnya lewat binding:
Di kode sungguhan, semua INSERT dan SELECT akan memakai placeholder. CLI hanya tidak punya aplikasi yang bisa melakukan bind, jadi literal di sini menggantikan hasil dari binding.
Berikutnya: Mencegah SQL Injection
Binding parameter adalah mekanismenya. Mengapa binding menghentikan SQL injection, dan beberapa kasus ketika binding saja tidak cukup, adalah topik halaman berikutnya.
Pertanyaan yang sering diajukan
Apa itu parameter binding di SQLite?
Parameter binding adalah cara memberikan nilai ke prepared statement secara terpisah dari teks SQL-nya. Kamu menulis placeholder seperti ? atau :name di SQL, lalu mengirim nilai sebenarnya lewat bind API milik driver. SQLite memperlakukan nilai yang di-bind murni sebagai data, dan nilai itu tidak pernah di-parse sebagai SQL.
Apa bedanya ? dan :name di SQLite?
? adalah placeholder posisional: nilai di-bind sesuai urutan kemunculannya. :name (juga @name dan $name) adalah placeholder bernama: kamu melakukan bind berdasarkan nama, bukan posisi. Parameter bernama lebih mudah dibaca dan diatur ulang kalau nilainya lebih dari dua atau tiga.
Bagaimana cara bind nilai NULL di SQLite?
Kirim nilai null/None/nil dari bahasamu lewat API binding, dan driver akan mengubahnya menjadi NULL SQL secara otomatis. Jangan pernah menulis string 'NULL', dan jangan pernah menyisipkan kata NULL ke teks SQL. Inti dari binding adalah menjauhkan nilai dari parser SQL.
Bisakah saya mencampur parameter posisional dan bernama dalam satu pernyataan?
SQLite mengizinkannya, tapi jangan lakukan. Pernyataan yang memakai placeholder ? dan :name sekaligus sulit dibaca dan mudah salah di-bind. Pilih satu gaya per pernyataan, biasanya parameter bernama kalau nilainya sudah lebih dari dua atau tiga.