Query laporan yang kita susun — misalnya rekap gaji per departemen — sering kali ditulis berulang di banyak tempat: sekali di skrip laporan mingguan, sekali di dashboard internal, lalu sekali lagi di query analisis ad-hoc. Setiap salinan menjadi beban pemeliharaan karena satu perubahan kecil pada logika laporan harus diterapkan ke semua salinan secara manual. MySQL menyediakan VIEW untuk masalah ini: sebuah query SELECT yang disimpan di dalam database dengan nama sendiri, lalu dapat dipakai seolah-olah query tersebut adalah tabel biasa. Pada chapter ini kita membuat view pertama, membuktikan bahwa view selalu menampilkan data segar, menyusun view dengan filter dan agregasi, lalu memeriksa definisinya yang tersimpan di server.
Menyiapkan Tabel Latihan #
Kita pakai database latihan belajar_view dengan satu tabel karyawan berisi delapan baris data pegawai fiktif. Kolom tanggal_masuk sengaja bertipe DATE dengan nilai literal tetap supaya seluruh contoh deterministik dan dapat dibandingkan ulang kapan pun:
DROP DATABASE IF EXISTS belajar_view;
CREATE DATABASE belajar_view;
USE belajar_view;Output yang Dihasilkan:
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Database changedCREATE TABLE karyawan (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(60) NOT NULL,
gaji DECIMAL(10,2) NOT NULL,
departemen VARCHAR(30) NOT NULL,
tanggal_masuk DATE NOT NULL
);
INSERT INTO karyawan (nama, gaji, departemen, tanggal_masuk) VALUES
('Budi Santoso', 8500000.00, 'IT', '2021-03-01'),
('Siti Rahayu', 7200000.00, 'IT', '2022-07-15'),
('Andi Wijaya', 9500000.00, 'Keuangan', '2020-01-20'),
('Dewi Lestari', 6800000.00, 'Keuangan', '2023-02-10'),
('Rina Kurnia', 5500000.00, 'HR', '2023-09-05'),
('Tono Prasetyo', 7700000.00, 'IT', '2021-11-01'),
('Sari Wulandari', 6300000.00, 'HR', '2024-01-08'),
('Joko Susilo', 8900000.00, 'Pemasaran', '2022-04-12');Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 8 rows affected (0.00 sec)
Records: 8 Duplicates: 0 Warnings: 0Data ini memuat empat departemen — IT, Keuangan, HR, dan Pemasaran — sehingga contoh penyaringan dan pengelompokan di bawah punya bahan yang bervariasi. Baris data pegawai sengaja diurutkan berdasarkan id pada saat penyisipan agar pembacaan contoh berikutnya lebih mudah diikuti.
Query Tersimpan yang Berperilaku Seperti Tabel #
Pembuatan view memakai statement CREATE VIEW nama AS SELECT .... Query di bagian AS SELECT itulah yang disimpan sebagai definisi view, dan setiap kali view dipakai, MySQL menjalankan query tersebut lalu mengembalikan hasilnya:
CREATE VIEW v_karyawan AS
SELECT id, nama, gaji, departemen, tanggal_masuk FROM karyawan;Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)Setelah statement itu sukses, v_karyawan dapat diperlakukan seperti tabel biasa di statement SELECT:
SELECT * FROM v_karyawan ORDER BY id;Output yang Dihasilkan:
+----+----------------+------------+------------+---------------+
| id | nama | gaji | departemen | tanggal_masuk |
+----+----------------+------------+------------+---------------+
| 1 | Budi Santoso | 8500000.00 | IT | 2021-03-01 |
| 2 | Siti Rahayu | 7200000.00 | IT | 2022-07-15 |
| 3 | Andi Wijaya | 9500000.00 | Keuangan | 2020-01-20 |
| 4 | Dewi Lestari | 6800000.00 | Keuangan | 2023-02-10 |
| 5 | Rina Kurnia | 5500000.00 | HR | 2023-09-05 |
| 6 | Tono Prasetyo | 7700000.00 | IT | 2021-11-01 |
| 7 | Sari Wulandari | 6300000.00 | HR | 2024-01-08 |
| 8 | Joko Susilo | 8900000.00 | Pemasaran | 2022-04-12 |
+----+----------------+------------+------------+---------------+
8 rows in set (0.00 sec)Mari kita bedah apa yang sebenarnya terjadi pada kedua statement di atas: baris CREATE VIEW v_karyawan AS mendaftarkan objek baru bernama v_karyawan ke dalam database belajar_view, sementara query SELECT id, nama, gaji, departemen, tanggal_masuk FROM karyawan disimpan sebagai definisi objek tersebut — perhatikan bahwa tidak ada satu baris data pun yang disalin ke tempat lain. Ketika kita menjalankan SELECT * FROM v_karyawan, MySQL membuka definisi view, menjalankan ulang query tersimpan tersebut terhadap tabel karyawan, lalu memformat hasilnya sebagai tabel semu berkolom id, nama, gaji, departemen, dan tanggal_masuk. Hasilnya identik dengan SELECT id, nama, gaji, departemen, tanggal_masuk FROM karyawan ORDER BY id yang kita tulis sendiri.
Status view sebagai objek bertipe tabel dapat kita konfirmasi lewat SHOW FULL TABLES:
SHOW FULL TABLES;Output yang Dihasilkan:
+------------------------+------------+
| Tables_in_belajar_view | Table_type |
+------------------------+------------+
| karyawan | BASE TABLE |
| v_karyawan | VIEW |
+------------------------+------------+
2 rows in set (0.00 sec)Kolom Table_type membedakan dua objek di database ini: karyawan bertipe BASE TABLE (tabel nyata yang menyimpan baris di disk), sedangkan v_karyawan bertipe VIEW (query tersimpan tanpa data sendiri). Perintah SHOW TABLES tanpa kolom tipe juga menampilkan view — inilah sebabnya view perlu diberi nama yang jelas berbeda dari tabel, misalnya dengan awalan v_ atau _vw.
View Selalu Menampilkan Data Segar #
Kesalahpahaman paling umum tentang view adalah menganggapnya sebagai salinan data pada saat pembuatan — seperti hasil SELECT yang di-fotonya lalu disimpan. Kenyataannya kebalikannya: view tidak menyimpan baris data sama sekali, sehingga hasil SELECT dari view selalu mencerminkan kondisi tabel dasar terbaru. Kita buktikan dengan menambahkan satu pegawai baru ke tabel karyawan, bukan ke view:
INSERT INTO karyawan (nama, gaji, departemen, tanggal_masuk)
VALUES ('Farhan Maulana', 6250000.00, 'IT', '2026-01-10');Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)Baris baru itu kita sisipkan melalui tabel dasar, jadi view v_karyawan tidak tersentuh sama sekali. Mari kita baca ulang view yang sama:
SELECT * FROM v_karyawan ORDER BY id;Output yang Dihasilkan:
+----+----------------+------------+------------+---------------+
| id | nama | gaji | departemen | tanggal_masuk |
+----+----------------+------------+------------+---------------+
| 1 | Budi Santoso | 8500000.00 | IT | 2021-03-01 |
| 2 | Siti Rahayu | 7200000.00 | IT | 2022-07-15 |
| 3 | Andi Wijaya | 9500000.00 | Keuangan | 2020-01-20 |
| 4 | Dewi Lestari | 6800000.00 | Keuangan | 2023-02-10 |
| 5 | Rina Kurnia | 5500000.00 | HR | 2023-09-05 |
| 6 | Tono Prasetyo | 7700000.00 | IT | 2021-11-01 |
| 7 | Sari Wulandari | 6300000.00 | HR | 2024-01-08 |
| 8 | Joko Susilo | 8900000.00 | Pemasaran | 2022-04-12 |
| 9 | Farhan Maulana | 6250000.00 | IT | 2026-01-10 |
+----+----------------+------------+------------+---------------+
9 rows in set (0.00 sec)View yang dibuat jauh sebelum penyisipan kini menampilkan sembilan baris, termasuk Farhan Maulana dengan id 9. Mekanismenya sederhana: statement SELECT * FROM v_karyawan menjalankan ulang definisi view terhadap isi karyawan yang terbaru, jadi setiap perubahan pada tabel dasar — INSERT, UPDATE, maupun DELETE — langsung terlihat di view tanpa perintah sinkronisasi apa pun.
View dengan Filter WHERE Tetap Tersaring #
Definisi view boleh memuat klausa apa pun yang sah pada SELECT biasa, termasuk WHERE. Filter yang tertanam di dalam definisi menjadi bagian permanen dari view — siapa pun yang membaca view tidak bisa melewatkan filter tersebut. Kita buat view khusus pegawai IT:
CREATE VIEW v_karyawan_it AS
SELECT id, nama, gaji, departemen FROM karyawan WHERE departemen = 'IT';Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)SELECT * FROM v_karyawan_it ORDER BY id;Output yang Dihasilkan:
+----+----------------+------------+------------+
| id | nama | gaji | departemen |
+----+----------------+------------+------------+
| 1 | Budi Santoso | 8500000.00 | IT |
| 2 | Siti Rahayu | 7200000.00 | IT |
| 6 | Tono Prasetyo | 7700000.00 | IT |
| 9 | Farhan Maulana | 6250000.00 | IT |
+----+----------------+------------+------------+
4 rows in set (0.00 sec)View ini hanya menampilkan empat baris pegawai IT — Budi, Siti, Tono, dan Farhan — meskipun tabel karyawan sudah berisi sembilan baris dari empat departemen. Sekarang kita uji ketahanan filternya: sisipkan satu pegawai non-IT ke tabel dasar, lalu hitung isi kedua sisi:
INSERT INTO karyawan (nama, gaji, departemen, tanggal_masuk)
VALUES ('Gita Safitri', 5900000.00, 'Pemasaran', '2026-02-01');
SELECT COUNT(*) AS jumlah_baris_view FROM v_karyawan_it;
SELECT COUNT(*) AS jumlah_baris_tabel FROM karyawan;Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)
+-------------------+
| jumlah_baris_view |
+-------------------+
| 4 |
+-------------------+
1 row in set (0.00 sec)
+--------------------+
| jumlah_baris_tabel |
+--------------------+
| 10 |
+--------------------+
1 row in set (0.00 sec)Tabel dasar kini berisi sepuluh baris, tetapi view tetap melaporkan empat baris karena klausa WHERE departemen = 'IT' hidup di dalam definisi view dan dievaluasi ulang pada setiap pembacaan. Perilaku ini membuat view cocok sebagai “pintu standar” menuju data — misalnya tim yang hanya berhak memantau pegawai IT cukup diberi akses ke view ini tanpa perlu mengingat filternya setiap kali query.
View Laporan dengan Kolom Terhitung dan GROUP BY #
Kekuatan view semakin terasa ketika definisinya memuat kolom terhitung dan agregasi. Kita angkat pola laporan “rekap per departemen” yang selama ini kita tulis berulang menjadi satu objek bernama v_rekap_departemen:
CREATE VIEW v_rekap_departemen AS
SELECT departemen,
COUNT(*) AS jumlah_karyawan,
SUM(gaji) AS total_gaji
FROM karyawan
GROUP BY departemen;Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)View laporan ini selanjutnya bisa dibaca seperti membaca tabel rekapitulasi:
SELECT * FROM v_rekap_departemen ORDER BY departemen;Output yang Dihasilkan:
+------------+-----------------+-------------+
| departemen | jumlah_karyawan | total_gaji |
+------------+-----------------+-------------+
| HR | 2 | 11800000.00 |
| IT | 4 | 29650000.00 |
| Keuangan | 2 | 16300000.00 |
| Pemasaran | 2 | 14800000.00 |
+------------+-----------------+-------------+
4 rows in set (0.00 sec)Mari kita bedah isi definisinya baris per baris: SELECT departemen menjadikan kolom departemen sebagai kunci baris laporan; COUNT(*) AS jumlah_karyawan menghitung jumlah pegawai di tiap kelompok dan memberi nama kolom hasil yang ramah dibaca; SUM(gaji) AS total_gaji menjumlahkan gaji per kelompok dengan tipe DECIMAL yang menjaga presisi angka; klausa GROUP BY departemen memecah baris tabel menjadi kelompok per departemen sebelum agregasi dijalankan. Sepuluh baris data runtuh menjadi empat baris laporan — dan karena view, laporan ini selalu mutakhir: pegawai yang baru bergabung kemarin langsung terhitung hari ini tanpa mengubah apa pun.
Memeriksa Definisi View #
Definisi view yang tersimpan di server dapat kita lihat kembali dengan SHOW CREATE VIEW. Terminator \G dipakai agar hasilnya tampil vertikal karena definisi berupa satu baris panjang:
SHOW CREATE VIEW v_karyawan_it\GOutput yang Dihasilkan:
*************************** 1. row ***************************
View: v_karyawan_it
Create View: CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v_karyawan_it` AS select `karyawan`.`id` AS `id`,`karyawan`.`nama` AS `nama`,`karyawan`.`gaji` AS `gaji`,`karyawan`.`departemen` AS `departemen` from `karyawan` where (`karyawan`.`departemen` = 'IT')
character_set_client: utf8mb4
collation_connection: utf8mb4_0900_ai_ci
1 row in set (0.00 sec)MySQL menampilkan kembali query yang kita simpan dalam bentuk ternormalisasi: seluruh identifier dibungkus backtick, kolom ditulis dengan tabel asalnya, dan klausa WHERE ditulis dalam tanda kurung. Informasi di sekitar definisi juga terbaca — DEFINER mencatat siapa pembuat view, SQL SECURITY DEFINER menentukan view berjalan memakai hak akses pembuatnya, sementara character_set_client dan collation_connection merekam konteks karakter saat view dibuat. Bentuk ternormalisasi inilah yang dipakai server saat menjalankan view, bukan teks asli yang kita ketik.
Untuk pemeriksaan ringkas banyak view sekaligus, tabel sistem information_schema.VIEWS lebih praktis:
SELECT TABLE_NAME, CHECK_OPTION, IS_UPDATABLE
FROM information_schema.VIEWS
WHERE TABLE_SCHEMA = 'belajar_view'
ORDER BY TABLE_NAME;Output yang Dihasilkan:
+--------------------+--------------+--------------+
| TABLE_NAME | CHECK_OPTION | IS_UPDATABLE |
+--------------------+--------------+--------------+
| v_karyawan | NONE | YES |
| v_karyawan_it | NONE | YES |
| v_rekap_departemen | NONE | NO |
+--------------------+--------------+--------------+
3 rows in set (0.00 sec)Tiga view kita terdaftar dengan status masing-masing: kolom CHECK_OPTION bernilai NONE karena belum ada satu pun view yang memakai WITH CHECK OPTION, sedangkan kolom IS_UPDATABLE menandai v_rekap_departemen dengan NO — view yang mengandung GROUP BY dan agregat tidak bisa dipakai untuk mengubah data, aturan yang kita bahas tuntas pada chapter batasan view.
Membersihkan Database Latihan #
Latihan selesai dan seluruh objek di belajar_view — tabel maupun view — kita jatuhkan sekaligus:
DROP DATABASE belajar_view;Output yang Dihasilkan:
Query OK, 4 rows affected (0.00 sec)Referensi #
- CREATE VIEW Statement — MySQL 8.4 Reference Manual
- View Processing — MySQL 8.4 Reference Manual
- View Algorithms — MySQL 8.4 Reference Manual
View pertama kita kini berdiri: query tersimpan yang berperilaku seperti tabel, selalu segar, bisa memfilter baris, dan mampu merangkum laporan agregat. Kebutuhan pasti berubah — suatu saat kolom laporan perlu ditambah, filter diganti, atau query definisinya ditulis ulang total. MySQL menyediakan CREATE OR REPLACE VIEW dan ALTER VIEW untuk situasi tersebut; chapter berikutnya membahas keduanya sekaligus efek berantainya ketika tabel dasar berubah struktur.