10 Kesalahan Umum SQL yang Sering Dilakukan Pemula (Dan Cara Memperbaikinya)
TL;DR
Kesalahan paling fatal: lupa WHERE di UPDATE/DELETE. Yang sering: salah urutan clause, NULL handling, Cartesian product. Solusi: selalu test di dev dulu, pake transaction, dan validasi input.
Belajar dari Kesalahan
Semua orang pasti pernah bikin kesalahan waktu belajar SQL. Aku sendiri pernah hampir hapus semua data di production karena lupa WHERE clause. Untung ada backup.
Daripada kamu ngalamin hal yang sama, mending belajar dari kesalahan umum yang sering dilakukan pemula. Di artikel ini, kita bakal bahas 10 kesalahan paling sering beserta cara menghindarinya.
Kesalahan #1: Lupa WHERE di UPDATE/DELETE
Ini kesalahan paling fatal dan paling sering terjadi. Hasilnya? Semua data ke-update atau ke-delete.
Contoh Kesalahan
-- Niat mau update satu customer
UPDATE customers SET status = 'inactive';
-- Yang terjadi: SEMUA customer jadi inactive
-- Niat mau hapus order yang cancelled
DELETE FROM orders;
-- Yang terjadi: SEMUA orders terhapus
Kenapa Ini Terjadi?
Biasanya karena:
- Copy-paste query tapi lupa tambahin WHERE
- Terburu-buru submit query
- Testing di production environment
Cara Mencegah
- Selalu tulis WHERE clause dulu
-- Tulis ini dulu
DELETE FROM orders WHERE status = 'cancelled';
-- Baru jalanin kalau udah yakin
- Test dengan SELECT dulu
-- Lihat data yang bakal ke-affect
SELECT * FROM orders WHERE status = 'cancelled';
-- Kalau udah bener, baru DELETE
DELETE FROM orders WHERE status = 'cancelled';
- Gunakan TRANSACTION
BEGIN;
DELETE FROM orders WHERE status = 'cancelled';
-- Cek hasilnya
SELECT * FROM orders WHERE status = 'cancelled';
-- Kalau bener, COMMIT. Kalau salah, ROLLBACK
COMMIT; -- atau ROLLBACK;
- Enable safe mode di MySQL
SET SQL_SAFE_UPDATES = 1;
-- Sekarang UPDATE/DELETE tanpa WHERE bakal error
Kesalahan #2: Salah Urutan Clause SQL
SQL punya urutan clause yang strict. Salah urutan = error.
Contoh Kesalahan
-- SALAH: HAVING sebelum GROUP BY
SELECT kategori, COUNT(*)
FROM products
HAVING COUNT(*) > 5
GROUP BY kategori;
-- Error: syntax error
Urutan yang Benar
SELECT kolom
FROM tabel
WHERE kondisi_baris
GROUP BY kolom_grouping
HAVING kondisi_agregasi
ORDER BY kolom_urutan
LIMIT jumlah;
Inget urutan ini: S-F-W-G-H-O-L (Select From Where Group Having Order Limit)
Tips Mengingat
Bikin akronim atau cerita:
- "Siti Fatimah Waktu Guru Hadir Order Late"
- Atau: "Selasa Februari Wawan Gi Home Only Learning"
Kesalahan #3: Tidak Menggunakan Alias dengan Benar
Alias bikin query lebih readable, tapi sering salah pakenya.
Contoh Kesalahan
-- SALAH: Pake alias sebelum didefinisiin
SELECT nama, total_order
FROM customers
WHERE total_order > 10; -- Error! total_order belum ada
-- Karena alias didefinisiin di SELECT, tapi WHERE dieksekusi duluan
Solusi
-- Pake subquery atau CTE
SELECT *
FROM (
SELECT nama, COUNT(*) as total_order
FROM customers c
JOIN orders o ON c.id = o.customer_id
GROUP BY nama
) AS subq
WHERE total_order > 10;
-- Atau pake HAVING (kalau filter hasil agregasi)
SELECT nama, COUNT(*) as total_order
FROM customers c
JOIN orders o ON c.id = o.customer_id
GROUP BY nama
HAVING COUNT(*) > 10;
Best Practice untuk Alias
-- Kasih alias yang meaningful
SELECT
c.nama AS nama_customer, -- Jelas
COUNT(o.id) AS jumlah_order, -- Deskriptif
SUM(o.total) AS total_belanja
FROM customers AS c -- AS opsional tapi lebih jelas
JOIN orders AS o ON c.id = o.customer_id
GROUP BY c.nama;
Kesalahan #4: Cartesian Product Tanpa Sengaja
Ini terjadi kalau kamu JOIN tabel tanpa kondisi ON yang proper.
Contoh Kesalahan
-- SALAH: Lupa ON clause
SELECT *
FROM customers, orders;
-- Hasil: Setiap customer dipasangkan dengan SETIAP order
-- 100 customers x 1000 orders = 100,000 rows!
-- SALAH: Kondisi JOIN yang salah
SELECT *
FROM customers c
JOIN orders o ON c.nama = o.nama; -- Harusnya c.id = o.customer_id
Cara Mencegah
- Selalu pake explicit JOIN syntax
-- BENAR
SELECT *
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id;
- Cek jumlah rows sebelum run query besar
-- Cek dulu estimasi hasilnya
SELECT COUNT(*)
FROM customers c
JOIN orders o ON c.id = o.customer_id;
- Pake LIMIT saat testing
SELECT *
FROM customers c
JOIN orders o ON c.id = o.customer_id
LIMIT 10;
Kesalahan #5: NULL Handling yang Salah
NULL di SQL itu tricky. NULL bukan 0, bukan empty string, NULL itu "tidak diketahui".
Contoh Kesalahan
-- SALAH: NULL ga bisa di-compare pake =
SELECT * FROM users WHERE phone = NULL;
-- Hasil: 0 rows (padahal ada yang NULL)
-- SALAH: NULL ga sama dengan empty string
SELECT * FROM users WHERE phone = '';
-- Ini cuma dapet yang empty string, bukan NULL
Cara yang Benar
-- Pake IS NULL atau IS NOT NULL
SELECT * FROM users WHERE phone IS NULL;
SELECT * FROM users WHERE phone IS NOT NULL;
-- Pake COALESCE untuk default value
SELECT
nama,
COALESCE(phone, 'Tidak ada') AS phone
FROM users;
-- Pake NULLIF untuk convert value ke NULL
SELECT NULLIF(phone, '') AS phone FROM users;
-- Empty string jadi NULL
NULL dalam Agregasi
-- COUNT(*) hitung semua rows termasuk NULL
-- COUNT(kolom) skip NULL values
SELECT
COUNT(*) AS total_rows,
COUNT(phone) AS yang_punya_phone,
COUNT(*) - COUNT(phone) AS yang_null
FROM users;
NULL dalam Operasi Matematika
-- Anything + NULL = NULL
SELECT 100 + NULL; -- Hasil: NULL
-- Gunakan COALESCE
SELECT 100 + COALESCE(discount, 0) FROM products;
Kesalahan #6: Tidak Menggunakan Index
Index bikin query cepat, tapi banyak pemula ga paham pentingnya.
Contoh Masalah
-- Query lambat di tabel besar
SELECT * FROM orders WHERE customer_id = 12345;
-- Full table scan: 1 juta rows di-cek satu-satu
Solusi: Buat Index
-- Buat index di kolom yang sering di-filter
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
-- Sekarang query jadi jauh lebih cepat
SELECT * FROM orders WHERE customer_id = 12345;
-- Index scan: langsung ke data yang relevan
Kapan Perlu Index?
- Kolom yang sering di-WHERE
- Kolom yang sering di-JOIN
- Kolom yang sering di-ORDER BY
- Kolom dengan high cardinality (banyak unique values)
Kapan Index Malah Buruk?
- Tabel kecil (< 1000 rows)
- Kolom yang jarang di-query
- Kolom yang sering di-UPDATE
- Kolom dengan low cardinality (cuma beberapa unique values)
Kesalahan #7: SELECT * di Production
SELECT * gampang ditulis tapi banyak masalahnya.
Masalah dengan SELECT *
-- Ambil semua kolom
SELECT * FROM users;
- Performance: Ambil data yang ga diperlukan
- Network: Transfer data lebih besar
- Brittle code: Kalau ada kolom baru, bisa break aplikasi
- Security: Mungkin expose data sensitif
Best Practice
-- Sebutin kolom yang dibutuhkan aja
SELECT id, nama, email, created_at
FROM users;
Kapan SELECT * Boleh?
- Waktu exploring data di development
- Di subquery (tapi tetep lebih baik explicit)
- Quick debugging
Kesalahan #8: String Comparison Case Sensitivity
Ini sering bikin bingung karena behavior-nya beda di tiap database.
Contoh Masalah
-- Di PostgreSQL, ini case-sensitive
SELECT * FROM users WHERE nama = 'Budi';
-- Ga akan dapet 'BUDI' atau 'budi'
-- Di MySQL dengan collation default, ini case-insensitive
SELECT * FROM users WHERE nama = 'Budi';
-- Bisa dapet 'BUDI', 'budi', 'BuDi'
Solusi Universal
-- Pake LOWER() atau UPPER() untuk konsistensi
SELECT * FROM users WHERE LOWER(nama) = LOWER('Budi');
-- Atau pake ILIKE di PostgreSQL
SELECT * FROM users WHERE nama ILIKE 'budi';
Best Practice
-- Standardize waktu INSERT
INSERT INTO users (email) VALUES (LOWER('Budi@Email.com'));
-- Atau buat constraint
ALTER TABLE users
ADD CONSTRAINT email_lowercase
CHECK (email = LOWER(email));
Kesalahan #9: Date Format yang Tidak Konsisten
Masalah date format sering bikin query ga jalan atau hasil salah.
Contoh Masalah
-- Format Indonesia: DD-MM-YYYY
-- Format SQL standard: YYYY-MM-DD
-- SALAH: Ini bisa jadi ambigu
SELECT * FROM orders WHERE tanggal = '01-02-2024';
-- Ini 1 Feb atau 2 Jan?
Best Practice
-- Selalu pake ISO format: YYYY-MM-DD
SELECT * FROM orders WHERE tanggal = '2024-02-01';
-- Atau pake explicit conversion
SELECT * FROM orders
WHERE tanggal = TO_DATE('01-02-2024', 'DD-MM-YYYY');
Handling Timezone
-- Simpan dalam UTC
INSERT INTO events (waktu) VALUES (NOW() AT TIME ZONE 'UTC');
-- Convert ke WIB waktu display
SELECT
waktu AT TIME ZONE 'Asia/Jakarta' AS waktu_wib
FROM events;
Date Comparison
-- SALAH: Ini bisa miss data
SELECT * FROM orders WHERE tanggal = '2024-02-01';
-- Kalau tanggal ada time component, ga akan match
-- BENAR: Pake range
SELECT * FROM orders
WHERE tanggal >= '2024-02-01'
AND tanggal < '2024-02-02';
-- Atau pake DATE()
SELECT * FROM orders
WHERE DATE(tanggal) = '2024-02-01';
Kesalahan #10: Tidak Memvalidasi Input (SQL Injection)
Ini kesalahan keamanan yang serius. SQL injection bisa bikin hacker akses atau hapus data.
Contoh Vulnerable Code
username = request.get('username')
query = f"SELECT * FROM users WHERE username = '{username}'"
Contoh Attack
-- Input malicious:
-- username: '; DROP TABLE users; --
-- Query jadi:
SELECT * FROM users WHERE username = ''; DROP TABLE users; --'
-- Tabel users terhapus!
Cara Mencegah
- Pake Parameterized Queries
cursor.execute(
"SELECT * FROM users WHERE username = %s",
(username,)
)
session.query(User).filter(User.username == username)
- Validasi Input
import re
if not re.match(r'^[a-zA-Z0-9_]+$', username):
raise ValueError("Invalid username format")
- Pake ORM
ORM kayak SQLAlchemy, Django ORM, atau Prisma otomatis handle parameterization.
- Principle of Least Privilege
Database user aplikasi jangan dikasih permission DROP TABLE atau DELETE tanpa WHERE.
Bonus: Debugging Tips
1. Baca Error Message dengan Teliti
ERROR: column "nama_lengkap" does not exist
LINE 1: SELECT nama_lengkap FROM users
^
HINT: Perhaps you meant to reference the column "nama".
Error message biasanya kasih tau persis di mana masalahnya.
2. Pecah Query Kompleks
-- Query kompleks yang error
SELECT a.*, b.*, c.*
FROM a
JOIN b ON ...
JOIN c ON ...
WHERE ...
GROUP BY ...
HAVING ...
-- Pecah jadi bagian-bagian
SELECT * FROM a LIMIT 5; -- Test tabel a
SELECT * FROM b LIMIT 5; -- Test tabel b
SELECT * FROM a JOIN b ON ... LIMIT 5; -- Test join
-- dst
3. Pake EXPLAIN
EXPLAIN SELECT * FROM orders WHERE customer_id = 123;
-- Lihat apakah pake index atau full scan
4. Cek Data Types
-- Lihat struktur tabel
\d orders -- PostgreSQL
DESCRIBE orders; -- MySQL
Kesimpulan
10 kesalahan yang udah kita bahas:
- Lupa WHERE di UPDATE/DELETE - Paling fatal, selalu double-check
- Salah urutan clause - Inget S-F-W-G-H-O-L
- Alias yang salah - Pahami urutan eksekusi SQL
- Cartesian product - Selalu pake explicit JOIN dengan ON
- NULL handling - Pake IS NULL, bukan = NULL
- Tidak pake index - Index kolom yang sering di-filter
- SELECT * - Sebutin kolom yang dibutuhkan aja
- Case sensitivity - Pake LOWER() untuk konsistensi
- Date format - Standarkan ke ISO format YYYY-MM-DD
- SQL injection - Pake parameterized queries
Yang paling penting: selalu test di development environment dulu, pake transaction untuk operasi berbahaya, dan backup data secara rutin.
Semoga kamu bisa menghindari kesalahan-kesalahan ini ya! Happy querying!
Artikel Terkait
SQL vs NoSQL: Perbedaan, Kelebihan, dan Kapan Menggunakannya
Pahami perbedaan SQL dan NoSQL, kelebihan masing-masing, dan kapan waktu yang tepat menggunakannya dengan contoh dari tech companies Indonesia