Panduan lengkap 7 hari dengan praktek langsung — dari SELECT dasar hingga analisis bisnis profesional
belajar_sql.db
-- ============================================ -- DATABASE LATIHAN: SISTEM TOKO ONLINE -- ============================================ -- Tabel Pelanggan CREATE TABLE pelanggan ( id INTEGER PRIMARY KEY AUTOINCREMENT, nama VARCHAR(100) NOT NULL, email VARCHAR(100) UNIQUE, kota VARCHAR(50), umur INTEGER, jenis_kelamin VARCHAR(10), tanggal_daftar DATE, status VARCHAR(20) DEFAULT 'aktif' ); -- Tabel Kategori Produk CREATE TABLE kategori ( id INTEGER PRIMARY KEY AUTOINCREMENT, nama_kategori VARCHAR(50) NOT NULL, deskripsi TEXT ); -- Tabel Produk CREATE TABLE produk ( id INTEGER PRIMARY KEY AUTOINCREMENT, nama_produk VARCHAR(100) NOT NULL, kategori_id INTEGER, harga DECIMAL(12,2) NOT NULL, stok INTEGER DEFAULT 0, berat_gram INTEGER, tanggal_masuk DATE, FOREIGN KEY (kategori_id) REFERENCES kategori(id) ); -- Tabel Pesanan CREATE TABLE pesanan ( id INTEGER PRIMARY KEY AUTOINCREMENT, pelanggan_id INTEGER NOT NULL, tanggal_pesan DATE NOT NULL, total_harga DECIMAL(15,2), status_pesanan VARCHAR(20) DEFAULT 'pending', metode_bayar VARCHAR(30), alamat_kirim TEXT, FOREIGN KEY (pelanggan_id) REFERENCES pelanggan(id) ); -- Tabel Detail Pesanan CREATE TABLE detail_pesanan ( id INTEGER PRIMARY KEY AUTOINCREMENT, pesanan_id INTEGER NOT NULL, produk_id INTEGER NOT NULL, jumlah INTEGER NOT NULL, harga_satuan DECIMAL(12,2) NOT NULL, subtotal DECIMAL(15,2), FOREIGN KEY (pesanan_id) REFERENCES pesanan(id), FOREIGN KEY (produk_id) REFERENCES produk(id) ); -- Tabel Karyawan CREATE TABLE karyawan ( id INTEGER PRIMARY KEY AUTOINCREMENT, nama VARCHAR(100) NOT NULL, jabatan VARCHAR(50), departemen VARCHAR(50), gaji DECIMAL(12,2), tanggal_masuk DATE, manager_id INTEGER, FOREIGN KEY (manager_id) REFERENCES karyawan(id) ); -- ============================================ -- ISI DATA SAMPLE -- ============================================ INSERT INTO kategori (nama_kategori, deskripsi) VALUES ('Elektronik', 'Perangkat elektronik dan gadget'), ('Pakaian', 'Baju, celana, dan aksesoris fashion'), ('Makanan', 'Makanan ringan dan minuman'), ('Buku', 'Buku fisik dan digital'), ('Olahraga', 'Peralatan dan perlengkapan olahraga'); INSERT INTO pelanggan (nama, email, kota, umur, jenis_kelamin, tanggal_daftar, status) VALUES ('Andi Pratama','andi@email.com','Jakarta',28,'Pria','2024-01-15','aktif'), ('Budi Santoso','budi@email.com','Bandung',35,'Pria','2024-02-20','aktif'), ('Citra Dewi','citra@email.com','Surabaya',24,'Wanita','2024-03-10','aktif'), ('Dina Rahma','dina@email.com','Jakarta',30,'Wanita','2024-01-25','nonaktif'), ('Eko Wijaya','eko@email.com','Medan',42,'Pria','2024-04-05','aktif'), ('Fitri Yani','fitri@email.com','Bandung',27,'Wanita','2024-05-12','aktif'), ('Gilang Ramadhan','gilang@email.com','Jakarta',31,'Pria','2024-03-18','aktif'), ('Hana Pertiwi','hana@email.com','Yogyakarta',29,'Wanita','2024-06-01','aktif'), ('Irfan Hakim','irfan@email.com','Surabaya',38,'Pria','2024-02-14','nonaktif'), ('Jasmine Putri','jasmine@email.com','Bali',26,'Wanita','2024-07-20','aktif'), ('Kurnia Sandi','kurnia@email.com','Semarang',33,'Pria','2024-04-22','aktif'), ('Lina Marlina','lina@email.com','Jakarta',45,'Wanita','2024-01-08','aktif'), ('Mega Sari',NULL,'Bandung',22,'Wanita','2024-08-15','aktif'), ('Nanda Putra','nanda@email.com','Medan',36,'Pria','2024-05-30','aktif'), ('Oscar Tan','oscar@email.com','Jakarta',29,'Pria','2024-09-01','aktif'); INSERT INTO produk (nama_produk, kategori_id, harga, stok, berat_gram, tanggal_masuk) VALUES ('Laptop Gaming ASUS',1,15000000,10,2500,'2024-01-01'), ('Smartphone Samsung S24',1,12000000,25,200,'2024-02-15'), ('Headphone Sony WH-1000',1,3500000,30,250,'2024-01-20'), ('Kaos Polos Premium',2,89000,200,200,'2024-03-01'), ('Celana Jeans Slim Fit',2,250000,150,500,'2024-02-10'), ('Jaket Hoodie Unisex',2,175000,100,400,'2024-04-05'), ('Coklat Batang Import',3,35000,500,100,'2024-01-15'), ('Kopi Arabica 250gr',3,85000,300,250,'2024-03-20'), ('Buku Pemrograman Python',4,120000,50,350,'2024-02-28'), ('Novel Best Seller 2024',4,95000,80,300,'2024-05-10'), ('Sepatu Lari Nike',5,1200000,40,600,'2024-01-25'), ('Matras Yoga Premium',5,350000,60,1500,'2024-04-15'), ('Tablet iPad Air',1,9500000,15,460,'2024-06-01'), ('Kemeja Batik Modern',2,195000,120,250,'2024-03-15'), ('Teh Hijau Organik',3,45000,400,150,'2024-02-05'); INSERT INTO pesanan (pelanggan_id, tanggal_pesan, total_harga, status_pesanan, metode_bayar, alamat_kirim) VALUES (1,'2024-06-01',15089000,'selesai','Transfer Bank','Jl. Sudirman No.10, Jakarta'), (2,'2024-06-02',12250000,'selesai','E-Wallet','Jl. Braga No.5, Bandung'), (3,'2024-06-03',3585000,'dikirim','COD','Jl. Tunjungan No.8, Surabaya'), (1,'2024-06-05',250000,'selesai','E-Wallet','Jl. Sudirman No.10, Jakarta'), (5,'2024-06-07',1200000,'selesai','Transfer Bank','Jl. Sisingamangaraja No.15, Medan'), (6,'2024-06-10',175000,'pending','E-Wallet','Jl. Dago No.20, Bandung'), (7,'2024-06-12',9620000,'selesai','Kartu Kredit','Jl. Gatot Subroto No.30, Jakarta'), (8,'2024-06-15',445000,'dikirim','Transfer Bank','Jl. Malioboro No.12, Yogyakarta'), (3,'2024-06-18',12095000,'selesai','Kartu Kredit','Jl. Tunjungan No.8, Surabaya'), (10,'2024-06-20',350000,'pending','COD','Jl. Sunset Road No.7, Bali'), (11,'2024-06-22',15120000,'selesai','Transfer Bank','Jl. Pandanaran No.5, Semarang'), (1,'2024-06-25',85000,'selesai','E-Wallet','Jl. Sudirman No.10, Jakarta'), (12,'2024-07-01',24500000,'selesai','Kartu Kredit','Jl. Kemang No.18, Jakarta'), (2,'2024-07-03',350000,'dikirim','E-Wallet','Jl. Braga No.5, Bandung'), (14,'2024-07-05',1289000,'selesai','Transfer Bank','Jl. SM Raja No.22, Medan'), (15,'2024-07-08',3500000,'pending','Kartu Kredit','Jl. Rasuna Said No.9, Jakarta'), (7,'2024-07-10',250000,'selesai','COD','Jl. Gatot Subroto No.30, Jakarta'), (6,'2024-07-12',12000000,'dikirim','Transfer Bank','Jl. Dago No.20, Bandung'), (8,'2024-07-15',95000,'selesai','E-Wallet','Jl. Malioboro No.12, Yogyakarta'), (3,'2024-07-18',1550000,'selesai','Kartu Kredit','Jl. Tunjungan No.8, Surabaya'); INSERT INTO detail_pesanan (pesanan_id, produk_id, jumlah, harga_satuan, subtotal) VALUES (1,1,1,15000000,15000000),(1,4,1,89000,89000),(2,2,1,12000000,12000000), (2,5,1,250000,250000),(3,3,1,3500000,3500000),(3,8,1,85000,85000), (4,5,1,250000,250000),(5,11,1,1200000,1200000),(6,6,1,175000,175000), (7,13,1,9500000,9500000),(7,9,1,120000,120000),(8,12,1,350000,350000), (8,10,1,95000,95000),(9,2,1,12000000,12000000),(9,10,1,95000,95000), (10,12,1,350000,350000),(11,1,1,15000000,15000000),(11,9,1,120000,120000), (12,8,1,85000,85000),(13,1,1,15000000,15000000),(13,13,1,9500000,9500000), (14,12,1,350000,350000),(15,11,1,1200000,1200000),(15,4,1,89000,89000), (16,3,1,3500000,3500000),(17,5,1,250000,250000),(18,2,1,12000000,12000000), (19,10,1,95000,95000),(20,11,1,1200000,1200000),(20,12,1,350000,350000); INSERT INTO karyawan (nama, jabatan, departemen, gaji, tanggal_masuk, manager_id) VALUES ('Rudi Hartono','CEO','Direksi',50000000,'2020-01-01',NULL), ('Sari Indah','CTO','Teknologi',40000000,'2020-03-15',1), ('Tono Sugiarto','CFO','Keuangan',40000000,'2020-02-01',1), ('Umar Faruq','Lead Developer','Teknologi',25000000,'2021-06-01',2), ('Vina Amelia','Senior Developer','Teknologi',20000000,'2021-09-15',4), ('Wahyu Prabowo','Junior Developer','Teknologi',12000000,'2023-01-10',4), ('Xena Putri','Finance Manager','Keuangan',22000000,'2021-04-01',3), ('Yogi Saputra','Accountant','Keuangan',15000000,'2022-07-20',7), ('Zahra Nabila','Marketing Manager','Marketing',22000000,'2021-05-10',1), ('Arif Budiman','Marketing Staff','Marketing',10000000,'2023-03-01',9), ('Bella Safitri','HR Manager','HRD',22000000,'2021-08-15',1), ('Chandra Wijaya','HR Staff','HRD',10000000,'2023-06-01',11);
Ctrl + Enter (Mac: Cmd + Enter)
SELECT · WHERE · ORDER BY · LIMIT
-- Ambil SEMUA kolom SELECT * FROM pelanggan; -- Ambil kolom tertentu saja SELECT nama, email, kota FROM pelanggan; -- Beri nama alias pada kolom SELECT nama AS nama_pelanggan, email AS alamat_email FROM pelanggan;
* artinya "semua kolom". AS untuk
memberi alias. SQL tidak case-sensitive.
-- Lihat kota apa saja yang ada (tanpa duplikat) SELECT DISTINCT kota FROM pelanggan; -- Kombinasi unik kota dan jenis kelamin SELECT DISTINCT kota, jenis_kelamin FROM pelanggan;
| Operator | Arti | Contoh |
|---|---|---|
= |
Sama dengan | kota = 'Jakarta' |
!= atau <> |
Tidak sama | kota != 'Jakarta' |
> < |
Lebih besar / kecil | umur > 30 |
BETWEEN |
Antara X dan Y | harga BETWEEN 100000 AND 500000 |
-- Pelanggan di Jakarta SELECT * FROM pelanggan WHERE kota = 'Jakarta'; -- Umur lebih dari 30 SELECT * FROM pelanggan WHERE umur > 30; -- Harga antara 100rb - 500rb SELECT nama_produk, harga FROM produk WHERE harga BETWEEN 100000 AND 500000;
-- Wanita di Jakarta SELECT * FROM pelanggan WHERE kota = 'Jakarta' AND jenis_kelamin = 'Wanita'; -- Dari Jakarta ATAU Bandung SELECT * FROM pelanggan WHERE kota = 'Jakarta' OR kota = 'Bandung'; -- Kombinasi (SELALU pakai tanda kurung!) SELECT * FROM pelanggan WHERE (kota = 'Jakarta' OR kota = 'Bandung') AND umur > 25;
() saat gabungkan AND dan
OR agar hasilnya benar!
-- Lebih ringkas daripada banyak OR SELECT * FROM pelanggan WHERE kota IN ('Jakarta', 'Bandung', 'Surabaya');
| Pattern | Arti | Contoh Match |
|---|---|---|
'A%' |
Diawali A | Andi, Ahmad |
'%Putr%' |
Mengandung "Putr" | Jasmine Putri, Nanda Putra |
'Di__' |
Di + tepat 2 karakter | Dina, Didi |
SELECT * FROM pelanggan WHERE nama LIKE 'A%'; -- Diawali A SELECT * FROM pelanggan WHERE nama LIKE '%Putr%'; -- Mengandung Putr
SELECT * FROM pelanggan WHERE email IS NULL; -- Tanpa email SELECT * FROM pelanggan WHERE email IS NOT NULL; -- Punya email
= NULL. Selalu pakai
IS NULL / IS NOT NULL
-- Urutkan nama A-Z (ASC = naik, default) SELECT * FROM pelanggan ORDER BY nama ASC; -- Produk termahal dulu (DESC = turun) SELECT nama_produk, harga FROM produk ORDER BY harga DESC; -- Ambil 5 data pertama SELECT * FROM pelanggan LIMIT 5; -- 3 produk termahal SELECT nama_produk, harga FROM produk ORDER BY harga DESC LIMIT 3; -- Paging: skip 5, ambil 5 SELECT * FROM pelanggan LIMIT 5 OFFSET 5;
-- 1. Tampilkan nama & email semua pelanggan wanita -- 2. Produk harga 50rb-500rb, urutkan termurah -- 3. 5 pelanggan termuda -- 4. Nama mengandung 'i' dan tinggal di Jakarta -- 5. Semua kota unik, urutkan A-Z -- 6. Stok < 30 atau harga > 5 juta -- 7. Pelanggan yang tidak punya email -- 8. Pesanan 'selesai' dengan metode 'E-Wallet'
-- 1. SELECT nama, email FROM pelanggan WHERE jenis_kelamin = 'Wanita'; -- 2. SELECT nama_produk, harga FROM produk WHERE harga BETWEEN 50000 AND 500000 ORDER BY harga ASC; -- 3. SELECT nama, umur FROM pelanggan ORDER BY umur ASC LIMIT 5; -- 4. SELECT * FROM pelanggan WHERE nama LIKE '%i%' AND kota = 'Jakarta'; -- 5. SELECT DISTINCT kota FROM pelanggan ORDER BY kota ASC; -- 6. SELECT nama_produk, stok, harga FROM produk WHERE stok < 30 OR harga > 5000000; -- 7. SELECT * FROM pelanggan WHERE email IS NULL; -- 8. SELECT * FROM pesanan WHERE status_pesanan = 'selesai' AND metode_bayar = 'E-Wallet';
COUNT · SUM · AVG · MIN · MAX · GROUP BY · HAVING
SELECT COUNT(*) AS total_pelanggan FROM pelanggan; SELECT COUNT(email) AS punya_email FROM pelanggan; -- NULL tak dihitung SELECT SUM(harga * stok) AS nilai_inventaris FROM produk; SELECT AVG(umur) AS rata_rata_umur FROM pelanggan; SELECT MIN(harga) AS termurah, MAX(harga) AS termahal FROM produk;
GROUP BY kota = pisahkan
per kota, lalu hitung tiap tumpukan.
-- Jumlah pelanggan per kota SELECT kota, COUNT(*) AS jumlah FROM pelanggan GROUP BY kota; -- Total penjualan per metode bayar SELECT metode_bayar, COUNT(*) AS transaksi, SUM(total_harga) AS total FROM pesanan GROUP BY metode_bayar; -- Pelanggan dan total belanjanya SELECT pelanggan_id, COUNT(*) AS jumlah_pesanan, SUM(total_harga) AS total FROM pesanan GROUP BY pelanggan_id ORDER BY total DESC;
WHERE = filter baris sebelum group.
HAVING = filter setelah group.
-- Kota dengan > 2 pelanggan SELECT kota, COUNT(*) AS jumlah FROM pelanggan GROUP BY kota HAVING COUNT(*) > 2; -- Kombinasi WHERE + HAVING SELECT pelanggan_id, SUM(total_harga) AS total FROM pesanan WHERE status_pesanan = 'selesai' -- filter BARIS dulu GROUP BY pelanggan_id HAVING SUM(total_harga) > 5000000 -- filter GRUP ORDER BY total DESC;
-- 1. Total pesanan dan total pendapatan keseluruhan -- 2. Jumlah produk per kategori -- 3. Total penjualan per bulan (hint: SUBSTR pada tanggal) -- 4. Metode bayar dengan total > 15 juta -- 5. Kota dengan rata-rata umur > 30 -- 6. Top 3 pelanggan by total belanja
-- 1. SELECT COUNT(*) AS total_pesanan, SUM(total_harga) AS pendapatan FROM pesanan; -- 2. SELECT kategori_id, COUNT(*) AS jumlah FROM produk GROUP BY kategori_id; -- 3. SELECT SUBSTR(tanggal_pesan,1,7) AS bulan, SUM(total_harga) AS total FROM pesanan GROUP BY SUBSTR(tanggal_pesan,1,7) ORDER BY bulan; -- 4. SELECT metode_bayar, SUM(total_harga) AS total FROM pesanan GROUP BY metode_bayar HAVING SUM(total_harga) > 15000000; -- 5. SELECT kota, AVG(umur) AS rata_umur FROM pelanggan GROUP BY kota HAVING AVG(umur) > 30; -- 6. SELECT pelanggan_id, SUM(total_harga) AS total FROM pesanan GROUP BY pelanggan_id ORDER BY total DESC LIMIT 3;
INNER JOIN · LEFT JOIN · SELF JOIN · Multi-table
| Jenis | Hasil | Kapan Pakai? |
|---|---|---|
INNER JOIN |
Hanya data yang cocok di kedua tabel | Saat hanya butuh data yang ada relasi |
LEFT JOIN |
Semua data kiri + cocok kanan (NULL jika tidak ada) | Saat mau lihat semua data kiri, termasuk yang tidak punya relasi |
SELF JOIN |
Gabungkan tabel dengan dirinya sendiri | Data hierarki (karyawan → manager) |
CROSS JOIN |
Semua kombinasi (Cartesian product) | Jarang, untuk referensi silang |
-- Pesanan + nama pelanggannya SELECT p.id, pel.nama, p.tanggal_pesan, p.total_harga FROM pesanan p INNER JOIN pelanggan pel ON p.pelanggan_id = pel.id; -- Produk + nama kategorinya SELECT pr.nama_produk, k.nama_kategori, pr.harga FROM produk pr INNER JOIN kategori k ON pr.kategori_id = k.id; -- Detail pesanan lengkap: pesanan + pelanggan + produk SELECT pes.id AS no_pesanan, pel.nama, pro.nama_produk, dp.jumlah, dp.subtotal FROM detail_pesanan dp INNER JOIN pesanan pes ON dp.pesanan_id = pes.id INNER JOIN pelanggan pel ON pes.pelanggan_id = pel.id INNER JOIN produk pro ON dp.produk_id = pro.id;
-- Semua pelanggan + pesanan (termasuk yg belum pesan) SELECT pel.nama, pes.id, pes.total_harga FROM pelanggan pel LEFT JOIN pesanan pes ON pel.id = pes.pelanggan_id; -- Cari pelanggan yang BELUM PERNAH pesan SELECT pel.nama, pel.email FROM pelanggan pel LEFT JOIN pesanan pes ON pel.id = pes.pelanggan_id WHERE pes.id IS NULL;
-- Karyawan dan nama managernya SELECT k.nama AS karyawan, k.jabatan, m.nama AS manager FROM karyawan k LEFT JOIN karyawan m ON k.manager_id = m.id;
-- Total belanja setiap pelanggan (dengan nama) SELECT pel.nama, COUNT(pes.id) AS jml_pesanan, COALESCE(SUM(pes.total_harga), 0) AS total_belanja FROM pelanggan pel LEFT JOIN pesanan pes ON pel.id = pes.pelanggan_id GROUP BY pel.id, pel.nama ORDER BY total_belanja DESC; -- Penjualan per kategori SELECT k.nama_kategori, SUM(dp.subtotal) AS total_pendapatan FROM kategori k JOIN produk pr ON k.id = pr.kategori_id JOIN detail_pesanan dp ON pr.id = dp.produk_id GROUP BY k.nama_kategori ORDER BY total_pendapatan DESC;
-- 1. Semua pesanan + nama pelanggan + kota -- 2. Pelanggan yang belum pernah pesan -- 3. Detail pesanan lengkap (pelanggan, produk, kategori, subtotal) -- 4. Total pendapatan per kategori produk -- 5. Karyawan + nama manager-nya -- 6. Produk yang belum pernah dipesan
-- 1. SELECT pes.id, pel.nama, pel.kota, pes.tanggal_pesan, pes.total_harga FROM pesanan pes JOIN pelanggan pel ON pes.pelanggan_id = pel.id; -- 2. SELECT pel.nama FROM pelanggan pel LEFT JOIN pesanan pes ON pel.id = pes.pelanggan_id WHERE pes.id IS NULL; -- 3. SELECT pel.nama, k.nama_kategori, pr.nama_produk, dp.jumlah, dp.subtotal FROM detail_pesanan dp JOIN pesanan pes ON dp.pesanan_id = pes.id JOIN pelanggan pel ON pes.pelanggan_id = pel.id JOIN produk pr ON dp.produk_id = pr.id JOIN kategori k ON pr.kategori_id = k.id; -- 4. SELECT k.nama_kategori, SUM(dp.subtotal) AS total FROM kategori k JOIN produk pr ON k.id = pr.kategori_id JOIN detail_pesanan dp ON pr.id = dp.produk_id GROUP BY k.nama_kategori ORDER BY total DESC; -- 5. SELECT k.nama AS karyawan, k.jabatan, COALESCE(m.nama,'-') AS manager FROM karyawan k LEFT JOIN karyawan m ON k.manager_id = m.id; -- 6. SELECT pr.nama_produk FROM produk pr LEFT JOIN detail_pesanan dp ON pr.id = dp.produk_id WHERE dp.id IS NULL;
Subquery · CASE WHEN · String · Date · COALESCE
-- Produk di atas harga rata-rata SELECT nama_produk, harga FROM produk WHERE harga > (SELECT AVG(harga) FROM produk); -- Pelanggan yang pernah pesan produk elektronik SELECT DISTINCT pel.nama FROM pelanggan pel WHERE pel.id IN ( SELECT pes.pelanggan_id FROM pesanan pes JOIN detail_pesanan dp ON pes.id = dp.pesanan_id JOIN produk pr ON dp.produk_id = pr.id WHERE pr.kategori_id = 1 );
-- Rata-rata jumlah pesanan per pelanggan SELECT AVG(jumlah_pesanan) AS rata_rata FROM ( SELECT pelanggan_id, COUNT(*) AS jumlah_pesanan FROM pesanan GROUP BY pelanggan_id ) AS ringkasan;
-- Pelanggan yang PERNAH pesan SELECT nama FROM pelanggan pel WHERE EXISTS (SELECT 1 FROM pesanan pes WHERE pes.pelanggan_id = pel.id); -- Pelanggan yang BELUM PERNAH pesan SELECT nama FROM pelanggan pel WHERE NOT EXISTS (SELECT 1 FROM pesanan pes WHERE pes.pelanggan_id = pel.id);
-- Kategorisasi harga produk SELECT nama_produk, harga, CASE WHEN harga < 100000 THEN 'Murah' WHEN harga BETWEEN 100000 AND 1000000 THEN 'Menengah' WHEN harga BETWEEN 1000001 AND 10000000 THEN 'Mahal' ELSE 'Premium' END AS level_harga FROM produk ORDER BY harga; -- Pivot manual: hitung per status SELECT COUNT(CASE WHEN status_pesanan = 'selesai' THEN 1 END) AS selesai, COUNT(CASE WHEN status_pesanan = 'dikirim' THEN 1 END) AS dikirim, COUNT(CASE WHEN status_pesanan = 'pending' THEN 1 END) AS pending FROM pesanan;
SELECT UPPER(nama), LOWER(email), LENGTH(nama) FROM pelanggan; SELECT SUBSTR(nama,1,5) AS lima_huruf FROM pelanggan; -- Potong teks SELECT REPLACE(email,'@email.com','@gmail.com') FROM pelanggan; SELECT nama || ' (' || kota || ')' AS info FROM pelanggan; -- Gabung teks
-- PPN 11% SELECT nama_produk, harga, ROUND(harga * 1.11, 0) AS setelah_ppn FROM produk; -- Ambil bagian tanggal SELECT STRFTIME('%Y', tanggal_pesan) AS tahun, STRFTIME('%m', tanggal_pesan) AS bulan FROM pesanan; -- COALESCE: ganti NULL dengan default SELECT nama, COALESCE(email, 'Tidak ada') AS email FROM pelanggan;
-- 1. Produk di atas harga rata-rata + kategorinya -- 2. Kolom "level_harga" (Murah/Sedang/Mahal) tiap produk -- 3. Pelanggan yang pernah beli produk kategori "Pakaian" -- 4. Hitung jumlah pelanggan per generasi (Gen Z/Millennial/Gen X) -- 5. Nama pelanggan, email (NULL jadi 'N/A'), panjang nama
-- 1. SELECT pr.nama_produk, k.nama_kategori, pr.harga FROM produk pr JOIN kategori k ON pr.kategori_id = k.id WHERE pr.harga > (SELECT AVG(harga) FROM produk); -- 2. SELECT nama_produk, harga, CASE WHEN harga < 100000 THEN 'Murah' WHEN harga <= 1000000 THEN 'Sedang' ELSE 'Mahal' END AS level FROM produk ORDER BY harga; -- 3. SELECT DISTINCT pel.nama FROM pelanggan pel WHERE pel.id IN (SELECT pes.pelanggan_id FROM pesanan pes JOIN detail_pesanan dp ON pes.id = dp.pesanan_id JOIN produk pr ON dp.produk_id = pr.id WHERE pr.kategori_id = 2); -- 4. SELECT gen, COUNT(*) FROM (SELECT CASE WHEN umur < 25 THEN 'Gen Z' WHEN umur <= 34 THEN 'Millennial' ELSE 'Gen X' END AS gen FROM pelanggan) GROUP BY gen; -- 5. SELECT nama, COALESCE(email,'N/A') AS email, LENGTH(nama) FROM pelanggan;
INSERT · UPDATE · DELETE · BEGIN · COMMIT · ROLLBACK
-- Insert satu baris INSERT INTO pelanggan (nama, email, kota, umur, jenis_kelamin, tanggal_daftar) VALUES ('Putri Handayani', 'putri@email.com', 'Makassar', 25, 'Wanita', '2024-10-01'); -- Insert banyak sekaligus INSERT INTO pelanggan (nama, email, kota, umur, jenis_kelamin, tanggal_daftar) VALUES ('Rizky Aditya', 'rizky@email.com', 'Palembang', 32, 'Pria', '2024-10-05'), ('Sinta Dewi', 'sinta@email.com', 'Bali', 28, 'Wanita', '2024-10-10');
-- Update satu kolom UPDATE pelanggan SET kota = 'Tangerang' WHERE id = 1; -- Update beberapa kolom UPDATE pelanggan SET email = 'andi.new@email.com', status = 'nonaktif' WHERE id = 1; -- Update berdasarkan kondisi UPDATE produk SET harga = harga * 1.1 WHERE kategori_id = 3; -- Harga makanan +10%
DELETE FROM pelanggan WHERE id = 16; -- Tips aman: cek dulu! SELECT COUNT(*) FROM pelanggan WHERE status = 'nonaktif'; -- cek jumlah SELECT * FROM pelanggan WHERE status = 'nonaktif'; -- lihat data DELETE FROM pelanggan WHERE status = 'nonaktif'; -- baru hapus
BEGIN TRANSACTION; INSERT INTO pesanan (...) VALUES (...); -- Buat pesanan INSERT INTO detail_pesanan (...) VALUES (...); -- Tambah detail UPDATE produk SET stok = stok - 1 WHERE id = 1; -- Kurangi stok COMMIT; -- Simpan semua perubahan -- Jika error → ROLLBACK; (batalkan semua)
VIEW · INDEX · CTE · Window Functions
-- Buat view: ringkasan pelanggan CREATE VIEW v_ringkasan_pelanggan AS SELECT pel.nama, pel.kota, COUNT(pes.id) AS total_pesanan, COALESCE(SUM(pes.total_harga), 0) AS total_belanja FROM pelanggan pel LEFT JOIN pesanan pes ON pel.id = pes.pelanggan_id GROUP BY pel.id, pel.nama, pel.kota; -- Pakai view seperti tabel biasa! SELECT * FROM v_ringkasan_pelanggan WHERE total_belanja > 10000000;
CREATE INDEX idx_pelanggan_kota ON pelanggan(kota); CREATE INDEX idx_pesanan_pelanggan ON pesanan(pelanggan_id); CREATE INDEX idx_pesanan_tanggal ON pesanan(tanggal_pesan);
-- Pelanggan VIP (belanja > 10 juta) WITH pelanggan_vip AS ( SELECT pelanggan_id, SUM(total_harga) AS total FROM pesanan GROUP BY pelanggan_id HAVING SUM(total_harga) > 10000000 ) SELECT pel.nama, pel.kota, vip.total FROM pelanggan_vip vip JOIN pelanggan pel ON vip.pelanggan_id = pel.id;
-- ROW_NUMBER: nomor urut SELECT nama_produk, harga, ROW_NUMBER() OVER (ORDER BY harga DESC) AS ranking FROM produk; -- PARTITION BY: ranking PER KATEGORI SELECT k.nama_kategori, pr.nama_produk, pr.harga, ROW_NUMBER() OVER (PARTITION BY pr.kategori_id ORDER BY pr.harga DESC) AS ranking FROM produk pr JOIN kategori k ON pr.kategori_id = k.id; -- Running total (akumulasi) SELECT tanggal_pesan, total_harga, SUM(total_harga) OVER (ORDER BY tanggal_pesan) AS akumulasi FROM pesanan WHERE status_pesanan = 'selesai'; -- LAG: bandingkan dengan baris sebelumnya SELECT tanggal_pesan, total_harga, LAG(total_harga) OVER (ORDER BY tanggal_pesan) AS sebelumnya, total_harga - LAG(total_harga) OVER (ORDER BY tanggal_pesan) AS selisih FROM pesanan WHERE status_pesanan = 'selesai';
| Fungsi | Kegunaan |
|---|---|
ROW_NUMBER() |
Nomor urut unik |
RANK() |
Ranking (bisa skip angka jika sama) |
DENSE_RANK() |
Ranking tanpa skip |
LAG() / LEAD() |
Nilai baris sebelum/sesudah |
SUM() OVER() |
Running total |
NTILE(n) |
Bagi ke n grup sama rata |
Analisis Penjualan · Customer Analytics · Laporan Bisnis
SELECT tanggal_pesan, COUNT(*) AS pesanan, SUM(total_harga) AS total, AVG(total_harga) AS rata_rata FROM pesanan WHERE status_pesanan = 'selesai' GROUP BY tanggal_pesan ORDER BY tanggal_pesan;
WITH bulanan AS ( SELECT SUBSTR(tanggal_pesan,1,7) AS bulan, SUM(total_harga) AS total FROM pesanan GROUP BY SUBSTR(tanggal_pesan,1,7) ) SELECT bulan, total, LAG(total) OVER (ORDER BY bulan) AS bulan_lalu, ROUND((total - LAG(total) OVER (ORDER BY bulan)) * 100.0 / LAG(total) OVER (ORDER BY bulan), 2) AS growth_persen FROM bulanan;
WITH rfm AS ( SELECT pel.nama, pel.kota, COUNT(pes.id) AS frequency, SUM(pes.total_harga) AS monetary, MAX(pes.tanggal_pesan) AS last_order FROM pelanggan pel LEFT JOIN pesanan pes ON pel.id = pes.pelanggan_id GROUP BY pel.id, pel.nama, pel.kota ) SELECT *, CASE WHEN monetary > 15000000 AND frequency >= 3 THEN 'VIP' WHEN monetary > 5000000 THEN 'Premium' WHEN monetary > 0 THEN 'Regular' ELSE 'Inactive' END AS segmen FROM rfm ORDER BY monetary DESC;
SELECT p1.nama_produk AS produk_1, p2.nama_produk AS produk_2, COUNT(*) AS dibeli_bersamaan FROM detail_pesanan dp1 JOIN detail_pesanan dp2 ON dp1.pesanan_id = dp2.pesanan_id AND dp1.produk_id < dp2.produk_id JOIN produk p1 ON dp1.produk_id = p1.id JOIN produk p2 ON dp2.produk_id = p2.id GROUP BY p1.nama_produk, p2.nama_produk ORDER BY dibeli_bersamaan DESC;
WITH total AS ( SELECT SUM(total_harga) AS grand_total FROM pesanan WHERE status_pesanan = 'selesai' ) SELECT metode_bayar, COUNT(*) AS transaksi, SUM(total_harga) AS pendapatan, ROUND(SUM(total_harga)*100.0/(SELECT grand_total FROM total),2) AS persen FROM pesanan WHERE status_pesanan = 'selesai' GROUP BY metode_bayar ORDER BY pendapatan DESC;
WITH penjualan AS ( SELECT COUNT(*) AS total_pesanan, SUM(total_harga) AS revenue, AVG(total_harga) AS avg_order, COUNT(DISTINCT pelanggan_id) AS pelanggan_aktif FROM pesanan WHERE status_pesanan = 'selesai' ), top_produk AS ( SELECT pr.nama_produk, SUM(dp.subtotal) AS rev FROM detail_pesanan dp JOIN produk pr ON dp.produk_id = pr.id GROUP BY pr.id ORDER BY rev DESC LIMIT 1 ), top_pelanggan AS ( SELECT pel.nama, SUM(pes.total_harga) AS belanja FROM pesanan pes JOIN pelanggan pel ON pes.pelanggan_id = pel.id GROUP BY pel.id ORDER BY belanja DESC LIMIT 1 ) SELECT p.total_pesanan, p.revenue, ROUND(p.avg_order,0) AS avg_order, p.pelanggan_aktif, tp.nama_produk AS produk_terlaris, tpl.nama AS pelanggan_terbaik, tpl.belanja FROM penjualan p CROSS JOIN top_produk tp CROSS JOIN top_pelanggan tpl;
SELECT kolom yang ditampilkan FROM dari tabel mana JOIN gabungkan tabel lain WHERE filter baris GROUP BY kelompokkan HAVING filter kelompok ORDER BY urutkan LIMIT batasi jumlah
FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT Tips: Itulah kenapa WHERE tidak bisa pakai alias dari SELECT
= != > < >= <= Perbandingan AND OR NOT Logika IN (...) Cek dari daftar BETWEEN x AND y Rentang nilai IS NULL Cek data kosong LIKE 'A%' Pola teks
COUNT(*) Hitung semua baris COUNT(col) Hitung (skip NULL) SUM(col) Jumlahkan AVG(col) Rata-rata MIN(col) Terkecil MAX(col) Terbesar
INNER JOIN Cocok di kedua tabel LEFT JOIN Semua kiri + cocok kanan SELF JOIN Tabel dengan dirinya CROSS JOIN Semua kombinasi
ROW_NUMBER() Nomor urut unik RANK() Ranking (skip) DENSE_RANK() Ranking (no skip) LAG()/LEAD() Baris sebelum/sesudah SUM() OVER() Running total