Harga setelah diskon, sisa stok per lusin, pembulatan ke rupiah terdekat — semua perhitungan semacam itu sebaiknya diselesaikan di dalam query, bukan ditarik mentah-mentah ke aplikasi lalu dihitung ulang. MySQL menyediakan fungsi numerik bawaan yang bekerja di atas angka literal maupun kolom. Pada chapter ini kita berlatih fungsi-fungsi tersebut memakai tabel produk: pembulatan dengan ROUND dan TRUNCATE, pembulatan ke atas dan ke bawah dengan CEIL dan FLOOR, nilai mutlak, sisa bagi, pangkat, sampai perilaku pembagian yang sering mengejutkan programmer yang terbiasa dengan bahasa C atau Java.

Menyiapkan Tabel Latihan #

Kita pakai kembali database latihan belajar_fungsi dengan tabel produk yang memuat kolom harga DECIMAL(10,2) dan stok INT:

sql
DROP DATABASE IF EXISTS belajar_fungsi;
CREATE DATABASE belajar_fungsi;
USE belajar_fungsi;

Output yang Dihasilkan:

text
Query OK, 0 rows affected, 1 warning (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Database changed
sql
CREATE TABLE produk (
  id INT AUTO_INCREMENT PRIMARY KEY,
  nama VARCHAR(60) NOT NULL,
  harga DECIMAL(10,2) NOT NULL,
  stok INT NOT NULL
);

INSERT INTO produk (nama, harga, stok) VALUES
  ('Buku Tulis 38', 4500.00, 120),
  ('Pensil 2B', 3000.00, 200),
  ('Penggaris 30 cm', 2500.00, 55),
  ('Spidol Whiteboard', 6000.00, 8),
  ('Buku Gambar A4', 9000.00, 13),
  ('Kertas HVS A4', 25000.00, 5),
  ('Stipo Perekat', 6000.00, 30);

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Query OK, 7 rows affected (0.00 sec)
Records: 7  Duplicates: 0  Warnings: 0

Kolom harga memakai DECIMAL sehingga nilainya tersimpan persis seperti yang ditulis — berbeda dengan FLOAT yang menyimpan pendekatan biner. Pilihan tipe ini penting bagi fungsi pembulatan: hasil ROUND pada DECIMAL tetap berupa DECIMAL yang eksak, bukan angka floating point yang bisa mengandung selisih kecil.

Membulatkan dengan ROUND #

ROUND(angka) membulatkan ke bilangan bulat terdekat, dan ROUND(angka, n) membulatkan sampai n angka desimal:

sql
SELECT ROUND(3.567, 1) AS satu_desimal,
       ROUND(2.5) AS setengah_positif,
       ROUND(-2.5) AS setengah_negatif;

Output yang Dihasilkan:

text
+--------------+------------------+------------------+
| satu_desimal | setengah_positif | setengah_negatif |
+--------------+------------------+------------------+
|          3.6 |                3 |               -3 |
+--------------+------------------+------------------+
1 row in set (0.00 sec)

Ketiga hasilnya menunjukkan aturan pembulatan MySQL: nilai pecahan tepat setengah dibulatkan menjauhi nol, sehingga 2.5 menjadi 3 dan -2.5 menjadi -3. Penerapan pada data tabel, misalnya menghitung potongan 10% dari harga lalu membulatkannya ke dua desimal:

sql
SELECT nama, harga, ROUND(harga * 0.1, 2) AS potongan_diskon
FROM produk
ORDER BY id;

Output yang Dihasilkan:

text
+-------------------+----------+-----------------+
| nama              | harga    | potongan_diskon |
+-------------------+----------+-----------------+
| Buku Tulis 38     |  4500.00 |          450.00 |
| Pensil 2B         |  3000.00 |          300.00 |
| Penggaris 30 cm   |  2500.00 |          250.00 |
| Spidol Whiteboard |  6000.00 |          600.00 |
| Buku Gambar A4    |  9000.00 |          900.00 |
| Kertas HVS A4     | 25000.00 |         2500.00 |
| Stipo Perekat     |  6000.00 |          600.00 |
+-------------------+----------+-----------------+
7 rows in set (0.00 sec)

Tanpa ROUND, hasil harga * 0.1 akan tampil dengan tiga angka desimal seperti 450.000 karena perkalian dua DECIMAL menambah jumlah digit pecahan hasilnya. Dengan ROUND(..., 2) tampilannya rapi dua desimal — dan yang lebih penting, nilainya memang dibulatkan secara eksak sebelum dikirim ke aplikasi.

Memotong Desimal dengan TRUNCATE #

TRUNCATE(angka, n) memotong digit di belakang koma pada posisi ke-n tanpa membulatkan sama sekali:

sql
SELECT ROUND(3.567, 1) AS dibulatkan,
       TRUNCATE(3.567, 1) AS dipotong,
       TRUNCATE(-2.5, 0) AS negatif_dipotong;

Output yang Dihasilkan:

text
+------------+----------+------------------+
| dibulatkan | dipotong | negatif_dipotong |
+------------+----------+------------------+
|        3.6 |      3.5 |               -2 |
+------------+----------+------------------+
1 row in set (0.00 sec)

Angka 3.567 menjadi 3.6 oleh ROUND (digit berikutnya 6 mendorong ke atas), tetapi menjadi 3.5 oleh TRUNCATE — digit 67 dibuang begitu saja. Perbedaan ini terasa pada bilangan negatif: TRUNCATE(-2.5, 0) menghasilkan -2 karena pemotongan bergerak ke arah nol, sementara ROUND tadi menghasilkan -3. Untuk perhitungan uang yang harus konsisten aturan pemotongannya, pilih fungsi secara sadar, bukan kebetulan.

CEIL, CEILING, FLOOR, dan ABS #

CEIL() dan CEILING() adalah nama yang sama untuk satu fungsi: membulatkan ke atas ke bilangan bulat terdekat. FLOOR() membulatkan ke bawah, dan ABS() mengembalikan nilai mutlak:

sql
SELECT CEIL(2.1) AS ceil_21,
       CEILING(2.1) AS ceiling_21,
       FLOOR(2.9) AS floor_29,
       CEIL(-2.1) AS ceil_negatif,
       ABS(-5.5) AS nilai_mutlak;

Output yang Dihasilkan:

text
+---------+------------+----------+--------------+--------------+
| ceil_21 | ceiling_21 | floor_29 | ceil_negatif | nilai_mutlak |
+---------+------------+----------+--------------+--------------+
|       3 |          3 |        2 |           -2 |          5.5 |
+---------+------------+----------+--------------+--------------+
1 row in set (0.00 sec)

Perhatikan CEIL(-2.1) menghasilkan -2, bukan -3 — pembulatan ke atas pada garis bilangan bergerak ke kanan, dan untuk bilangan negatif itu berarti mendekati nol. ABS berguna saat kita menghitung selisih yang bisa negatif tetapi yang kita butuhkan hanya besarnya, misalnya deviasi stok aktual terhadap stok minimum.

MOD dan POWER #

MOD(a, b) mengembalikan sisa pembagian, identik dengan operator %. POWER(a, b) atau alias POW(a, b) menghitung pangkat. Contoh penerapan MOD pada data: membagi stok ke dalam pak lusin (12 potong):

sql
SELECT nama, stok,
       stok DIV 12 AS jumlah_lusin,
       MOD(stok, 12) AS sisa_di_luar_lusin
FROM produk
ORDER BY id;

Output yang Dihasilkan:

text
+-------------------+------+--------------+--------------------+
| nama              | stok | jumlah_lusin | sisa_di_luar_lusin |
+-------------------+------+--------------+--------------------+
| Buku Tulis 38     |  120 |           10 |                  0 |
| Pensil 2B         |  200 |           16 |                  8 |
| Penggaris 30 cm   |   55 |            4 |                  7 |
| Spidol Whiteboard |    8 |            0 |                  8 |
| Buku Gambar A4    |   13 |            1 |                  1 |
| Kertas HVS A4     |    5 |            0 |                  5 |
| Stipo Perekat     |   30 |            2 |                  6 |
+-------------------+------+--------------+--------------------+
7 rows in set (0.00 sec)

Stok 55 misalnya terbagi menjadi 4 lusin dengan sisa 7. POWER jarang dipakai untuk harga, tetapi berguna untuk perhitungan komposit seperti bunga:

sql
SELECT POWER(2, 10) AS dua_pangkat_sepuluh;

Output yang Dihasilkan:

text
+---------------------+
| dua_pangkat_sepuluh |
+---------------------+
|                1024 |
+---------------------+
1 row in set (0.00 sec)

Perilaku Pembagian: / vs DIV #

Bagian ini sering menjebak pendatang dari bahasa C, Java, atau JavaScript. Di MySQL, operator / selalu menghasilkan bilangan pecahan — pembagian dua integer tidak dipotong menjadi integer:

sql
SELECT 1/2 AS bagi_biasa,
       1.0/2 AS bagi_desimal,
       7 DIV 2 AS bagi_bulat;

Output yang Dihasilkan:

text
+------------+--------------+------------+
| bagi_biasa | bagi_desimal | bagi_bulat |
+------------+--------------+------------+
|     0.5000 |      0.50000 |          3 |
+------------+--------------+------------+
1 row in set (0.00 sec)

Hasil 1/2 adalah 0.5000, bukan 0 — MySQL mengembalikan DECIMAL dengan empat angka pecahan untuk pembagian dua integer. Ketika pembilangnya pecahan (1.0), skala hasilnya bertambah menjadi 0.50000. Bila kita memang butuh pembagian bilangan bulat yang dibuang pecahannya, operator khusus DIV itulah jawabannya: 7 DIV 2 menghasilkan 3.

RAND dan Seed-nya #

RAND() mengembalikan bilangan acak pecahan antara 0 dan 1. Tanpa argumen, hasilnya berbeda pada setiap pemanggilan sehingga contohnya tidak bisa ditampilkan dengan nilai tetap:

sql
-- @check
SELECT RAND() AS angka_acak;

Output yang Dihasilkan:

text
+--------------------+
| angka_acak         |
+--------------------+
| 0.5142688123892412 |
+--------------------+
1 row in set (0.00 sec)

Nilai di atas hanyalah salah satu hasil — angka yang tampil di layar kita pasti berbeda karena pemanggilan RAND() tanpa seed memakai keadaan internal server sebagai sumber acak. Namun RAND juga menerima seed: RAND(n) memulai generator angka acak dari titik awal yang sama, sehingga urutan angkanya dapat direproduksi:

sql
SELECT RAND(42) AS panggilan_pertama, RAND(42) AS panggilan_kedua;

Output yang Dihasilkan:

text
+--------------------+--------------------+
| panggilan_pertama  | panggilan_kedua    |
+--------------------+--------------------+
| 0.6629110161800972 | 0.6629110161800972 |
+--------------------+--------------------+
1 row in set (0.00 sec)

Kedua kolom menunjukkan angka yang persis sama, dan menjalankan blok ini berkali-kali tetap menghasilkan angka yang sama pula. Seed memutus hubungan dengan jam server, itulah sebabnya hasilnya stabil dan pantas dicantumkan sebagai contoh tertulis. Pola RAND(seed) berguna untuk membuat data uji acak yang dapat direproduksi — dua orang yang menjalankan skrip dengan seed sama akan mendapatkan data yang identik.

Membersihkan Database Latihan #

sql
DROP DATABASE belajar_fungsi;

Output yang Dihasilkan:

text
Query OK, 1 row affected (0.00 sec)

Kita kini bisa menghitung di dalam query dengan pembulatan yang terkendali. Berikutnya adalah keluarga fungsi yang jumlah anggotanya paling banyak di MySQL: fungsi tanggal dan waktu — memformat tanggal, menghitung selisih hari, menambah interval, dan mengekstrak bagian tertentu dari sebuah nilai DATE.

Referensi #