Membuat Dashboard Nilai Siswa dengan PHP dan MySQL
Dashboard nilai siswa dengan PHP dan MySQL adalah project latihan yang menggabungkan database relasional, form input, query agregasi, dan tampilan ringkasan. Jawaban cepatnya: buat tabel siswa, mapel, dan nilai, simpan nilai lewat prepared statement, lalu tampilkan rata-rata dan daftar nilai memakai JOIN, AVG(), dan GROUP BY.
Project ini cocok setelah kamu memahami Koneksi Database, SQL Lanjutan, Relasi Database, dan CRUD Lengkap. Fokus kita bukan membuat sistem sekolah lengkap, tetapi membuat fondasi dashboard yang jelas dan mudah dikembangkan.
Fitur yang Akan Dibuat
Dashboard sederhana ini punya fitur:
- Menyimpan data siswa.
- Menyimpan data mata pelajaran.
- Menginput nilai siswa per mapel.
- Menampilkan total siswa, total mapel, dan rata-rata nilai.
- Menampilkan tabel nilai lengkap dengan nama siswa dan mapel.
Struktur datanya sengaja dibuat berelasi agar kamu terbiasa memakai foreign key dan JOIN.
Struktur Folder
dashboard-nilai/
├── config/
│ └── database.php
├── index.php
├── input-nilai.php
└── simpan-nilai.php
Untuk project latihan, kita pisahkan halaman daftar dan halaman input agar alurnya mudah dipahami.
Membuat Database
CREATE DATABASE dashboard_nilai;
USE dashboard_nilai;
CREATE TABLE siswa (
id INT AUTO_INCREMENT PRIMARY KEY,
nis VARCHAR(20) NOT NULL UNIQUE,
nama VARCHAR(100) NOT NULL,
kelas VARCHAR(20) NOT NULL
);
CREATE TABLE mapel (
id INT AUTO_INCREMENT PRIMARY KEY,
nama_mapel VARCHAR(100) NOT NULL
);
CREATE TABLE nilai (
id INT AUTO_INCREMENT PRIMARY KEY,
siswa_id INT NOT NULL,
mapel_id INT NOT NULL,
nilai DECIMAL(5,2) NOT NULL,
semester VARCHAR(20) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (siswa_id) REFERENCES siswa(id) ON DELETE CASCADE,
FOREIGN KEY (mapel_id) REFERENCES mapel(id) ON DELETE CASCADE
);
Masukkan data awal:
INSERT INTO siswa (nis, nama, kelas) VALUES
('S001', 'Alya Putri', '10A'),
('S002', 'Bima Pratama', '10A'),
('S003', 'Citra Lestari', '10B');
INSERT INTO mapel (nama_mapel) VALUES
('Matematika'),
('Bahasa Indonesia'),
('Pemrograman Dasar');
Koneksi Database
Isi config/database.php:
<?php
declare(strict_types=1);
$pdo = new PDO(
'mysql:host=127.0.0.1;dbname=dashboard_nilai;charset=utf8mb4',
'root',
'',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
Jika muncul error SQLSTATE, jangan ditebak. Cocokkan nama database, username, password, dan status MySQL. Panduan lengkapnya ada di Cara Mengatasi SQLSTATE Error di PHP PDO.
Membuat Halaman Dashboard
Isi index.php:
<?php
require __DIR__ . '/config/database.php';
$totalSiswa = $pdo->query('SELECT COUNT(*) FROM siswa')->fetchColumn();
$totalMapel = $pdo->query('SELECT COUNT(*) FROM mapel')->fetchColumn();
$rataRata = $pdo->query('SELECT AVG(nilai) FROM nilai')->fetchColumn();
$stmt = $pdo->query("
SELECT
nilai.id,
siswa.nis,
siswa.nama,
siswa.kelas,
mapel.nama_mapel,
nilai.nilai,
nilai.semester
FROM nilai
INNER JOIN siswa ON siswa.id = nilai.siswa_id
INNER JOIN mapel ON mapel.id = nilai.mapel_id
ORDER BY siswa.nama ASC, mapel.nama_mapel ASC
");
$daftarNilai = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Dashboard Nilai Siswa</title>
</head>
<body>
<h1>Dashboard Nilai Siswa</h1>
<p><a href="input-nilai.php">+ Input Nilai</a></p>
<section>
<h2>Ringkasan</h2>
<ul>
<li>Total siswa: <?= (int) $totalSiswa ?></li>
<li>Total mapel: <?= (int) $totalMapel ?></li>
<li>Rata-rata nilai: <?= number_format((float) $rataRata, 2, ',', '.') ?></li>
</ul>
</section>
<table border="1" cellpadding="8">
<thead>
<tr>
<th>NIS</th>
<th>Nama</th>
<th>Kelas</th>
<th>Mapel</th>
<th>Nilai</th>
<th>Semester</th>
</tr>
</thead>
<tbody>
<?php foreach ($daftarNilai as $row): ?>
<tr>
<td><?= htmlspecialchars($row['nis']) ?></td>
<td><?= htmlspecialchars($row['nama']) ?></td>
<td><?= htmlspecialchars($row['kelas']) ?></td>
<td><?= htmlspecialchars($row['nama_mapel']) ?></td>
<td><?= number_format((float) $row['nilai'], 2, ',', '.') ?></td>
<td><?= htmlspecialchars($row['semester']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</body>
</html>
Bagian paling penting ada pada query JOIN. Tabel nilai hanya menyimpan ID, sedangkan nama siswa dan nama mapel diambil dari tabel masing-masing.
Isi input-nilai.php:
<?php
require __DIR__ . '/config/database.php';
$siswa = $pdo->query('SELECT id, nis, nama FROM siswa ORDER BY nama ASC')->fetchAll();
$mapel = $pdo->query('SELECT id, nama_mapel FROM mapel ORDER BY nama_mapel ASC')->fetchAll();
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Input Nilai</title>
</head>
<body>
<h1>Input Nilai Siswa</h1>
<form method="POST" action="simpan-nilai.php">
<label>
Siswa
<select name="siswa_id" required>
<option value="">Pilih siswa</option>
<?php foreach ($siswa as $item): ?>
<option value="<?= $item['id'] ?>">
<?= htmlspecialchars($item['nis'] . ' - ' . $item['nama']) ?>
</option>
<?php endforeach; ?>
</select>
</label>
<label>
Mata Pelajaran
<select name="mapel_id" required>
<option value="">Pilih mapel</option>
<?php foreach ($mapel as $item): ?>
<option value="<?= $item['id'] ?>">
<?= htmlspecialchars($item['nama_mapel']) ?>
</option>
<?php endforeach; ?>
</select>
</label>
<label>
Nilai
<input type="number" name="nilai" min="0" max="100" step="0.01" required>
</label>
<label>
Semester
<input type="text" name="semester" placeholder="Ganjil 2026" required>
</label>
<button type="submit">Simpan</button>
</form>
</body>
</html>
Dropdown siswa dan mapel diambil dari database, bukan diketik manual. Ini mencegah salah input ID.
Menyimpan Nilai
Isi simpan-nilai.php:
<?php
require __DIR__ . '/config/database.php';
$siswaId = (int) ($_POST['siswa_id'] ?? 0);
$mapelId = (int) ($_POST['mapel_id'] ?? 0);
$nilai = (float) ($_POST['nilai'] ?? -1);
$semester = trim($_POST['semester'] ?? '');
if ($siswaId <= 0 || $mapelId <= 0 || $nilai < 0 || $nilai > 100 || $semester === '') {
die('Data nilai tidak valid.');
}
$stmt = $pdo->prepare("
INSERT INTO nilai (siswa_id, mapel_id, nilai, semester)
VALUES (?, ?, ?, ?)
");
$stmt->execute([$siswaId, $mapelId, $nilai, $semester]);
header('Location: index.php');
exit;
Gunakan prepared statement agar input dari form tidak digabung mentah ke query SQL.
Menampilkan Rata-Rata per Siswa
Untuk dashboard yang lebih berguna, tambahkan query ini:
$stmt = $pdo->query("
SELECT
siswa.nis,
siswa.nama,
siswa.kelas,
AVG(nilai.nilai) AS rata_rata
FROM siswa
LEFT JOIN nilai ON nilai.siswa_id = siswa.id
GROUP BY siswa.id, siswa.nis, siswa.nama, siswa.kelas
ORDER BY rata_rata DESC
");
$ranking = $stmt->fetchAll();
LEFT JOIN membuat siswa tetap tampil walaupun belum punya nilai. Ini penting untuk dashboard, karena data kosong juga informasi.
Kesalahan yang Sering Terjadi
Menyimpan Nama Mapel Langsung di Tabel Nilai
Simpan mapel_id, bukan nama mapel. Kalau nama mapel berubah, kamu hanya mengubah satu baris di tabel mapel.
Tidak Memvalidasi Rentang Nilai
Nilai harus dibatasi, misalnya 0 sampai 100. Jangan percaya input browser saja, karena validasi HTML bisa dilewati.
Menggunakan Query Tanpa Prepared Statement
Jangan membuat query seperti "INSERT ... VALUES ($_POST[nilai])". Selalu pakai prepare() dan execute().
Lupa GROUP BY Saat Menghitung Rata-Rata
Kalau ingin rata-rata per siswa, query harus dikelompokkan berdasarkan siswa. Tanpa GROUP BY, hasilnya menjadi rata-rata semua data.
Tidak Menggunakan htmlspecialchars()
Nama siswa, kelas, dan semester tetap harus di-escape saat ditampilkan agar halaman aman dari XSS.
FAQ
Apakah dashboard ini perlu login?
Untuk latihan dasar, belum wajib. Kalau ingin dipakai serius, tambahkan login admin seperti di Membuat Login Multi-Role dengan PHP untuk Pemula.
Apakah satu siswa boleh punya dua nilai untuk mapel yang sama?
Bisa, kalau semester atau jenis ujian berbeda. Kalau ingin mencegah duplikasi, tambahkan unique key gabungan seperti (siswa_id, mapel_id, semester).
Kenapa memakai DECIMAL(5,2) untuk nilai?
Agar nilai seperti 87.50 bisa disimpan rapi. Kalau sekolah hanya memakai angka bulat, TINYINT UNSIGNED juga cukup.
Bagaimana cara menambah filter kelas?
Tambahkan parameter kelas dari query string, lalu tambahkan WHERE siswa.kelas = ? dengan prepared statement.
Bacaan Terkait