Hampir semua tabel menyimpan bilangan bulat: jumlah stok barang, nomor telepon tanpa nol depan, skor ulasan, dan — khusus di Indonesia — harga dalam rupiah yang memang tidak mengenal pecahan. PostgreSQL menyediakan tiga ukuran tipe integer, dan memilih yang tepat sejak awal lebih penting dari yang terlihat: pilih terlalu kecil dan aplikasi meledak dua tahun kemudian karena error overflow, pilih terlalu besar dan tiap baris membayar penyimpanan yang tidak perlu. Chapter ini membedah ketiganya, membuktikan rentangnya di server sungguhan, lalu menghadapi dua jebakan klasik: overflow saat INSERT dan pembagian yang membulat ke bawah.
Tiga Ukuran Integer #
PostgreSQL membedakan bilangan bulat berdasarkan berapa byte yang dipakai untuk menyimpannya. Semakin besar penyimpanan, semakin lebar rentang nilainya:
| Tipe | Penyimpanan | Rentang | Alias |
|---|---|---|---|
smallint | 2 byte | -32.768 sampai 32.767 | int2 |
integer | 4 byte | -2.147.483.648 sampai 2.147.483.647 | int, int4 |
bigint | 8 byte | -9.223.372.036.854.775.808 sampai 9.223.372.036.854.775.807 | int8 |
Angka rentang itu bukan karangan acak: smallint memakai 16 bit sehingga maksimalnya 2^15 - 1 = 32.767; integer memakai 32 bit (2^31 - 1 = 2.147.483.647); dan bigint memakai 64 bit. Setiap ukuran menyimpan setengah rentangnya untuk bilangan negatif, itulah mengapa batas bawahnya selalu satu angka lebih jauh dari batas atasnya.
Sebagai patokan praktis: smallint cukup untuk hitungan yang pasti kecil (usia, lantai gedung, stok rak), integer adalah pilihan default yang menampung lebih dari dua miliar, dan bigint dipakai untuk penghitung besar seperti jumlah tayangan video atau populasi dunia.
Membuat Tabel dengan Tipe Integer #
Kita buktikan ketiga tipe bekerja dalam satu latihan gudang. Tabel barang memakai smallint untuk stok per rak dan integer untuk harga rupiah; tabel video memakai bigint karena jumlah tayangan sebuah video populer bisa melewati batas dua miliar:
CREATE TABLE barang (
id integer PRIMARY KEY,
nama text NOT NULL,
stok smallint NOT NULL DEFAULT 0,
harga integer NOT NULL
);
INSERT INTO barang (id, nama, stok, harga) VALUES
(1, 'Buku Tulis', 120, 4500),
(2, 'Laptop Kantor', 15, 8500000),
(3, 'Kertas HVS A4', 32767, 55000);
SELECT id, nama, stok, harga
FROM barang
ORDER BY id;
CREATE TABLE video (
id integer PRIMARY KEY,
judul text NOT NULL,
jumlah_tayangan bigint NOT NULL DEFAULT 0
);
INSERT INTO video (id, judul, jumlah_tayangan) VALUES
(1, 'Belajar PostgreSQL dari Nol', 3250000000),
(2, 'Tur Keliling Toko Maju Jaya', 1500000);
SELECT id, judul, jumlah_tayangan
FROM video
ORDER BY id;Output yang Dihasilkan:
CREATE TABLE
INSERT 0 3
id | nama | stok | harga
----+---------------+-------+---------
1 | Buku Tulis | 120 | 4500
2 | Laptop Kantor | 15 | 8500000
3 | Kertas HVS A4 | 32767 | 55000
(3 rows)
CREATE TABLE
INSERT 0 2
id | judul | jumlah_tayangan
----+-----------------------------+-----------------
1 | Belajar PostgreSQL dari Nol | 3250000000
2 | Tur Keliling Toko Maju Jaya | 1500000
(2 rows)Mari kita bedah pilihan tipenya baris demi baris:
stok smallint NOT NULL DEFAULT 0: isi satu rak toko tidak mungkin mendekati 32.767, jadi 2 byte per nilai sudah memadai. Baris Kertas HVS A4 sengaja diisi32767— tepat di batas atassmallint— untuk membuktikan nilai itu masih sah.harga integer NOT NULL: harga rupiah ditulis tanpa pecahan, danintegermenampung hingga Rp2,1 miliar per barang — lebih dari cukup untuk isi toko. Barang senilai di atasnya (properti, kendaraan berat) sebaiknya pindah kebigint.jumlah_tayangan bigint NOT NULL DEFAULT 0: angka3250000000(3,25 miliar) sengaja dipilih melewati batasinteger; hanyabigintyang bisa menampungnya tanpa error.
Membuktikan Rentang Batas #
Batas atas dan batas bawah setiap tipe bisa kita lihat langsung sebagai nilai yang sah. Tiga query berikut masing-masing menampilkan sepasang batas smallint, integer, dan bigint — perhatikan bahwa seluruhnya diterima server tanpa keluhan:
SELECT 32767::smallint AS batas_atas, (-32768)::smallint AS batas_bawah;
SELECT 2147483647::integer AS batas_atas, (-2147483648)::integer AS batas_bawah;
SELECT 9223372036854775807::bigint AS batas_atas, (-9223372036854775808)::bigint AS batas_bawah;Output yang Dihasilkan:
batas_atas | batas_bawah
------------+-------------
32767 | -32768
(1 row)
batas_atas | batas_bawah
------------+-------------
2147483647 | -2147483648
(1 row)
batas_atas | batas_bawah
---------------------+----------------------
9223372036854775807 | -9223372036854775808
(1 row)Notasi 32767::smallint adalah cast eksplisit: literal 32767 yang semula bertipe numeric diubah menjadi smallint. Tanda kurung pada (-2147483648)::integer penting — tanpa kurung, operator cast mengikat lebih dulu ke literal 2147483648 yang sendirian sudah melebihi batas integer, sehingga error terjadi sebelum tanda minus sempat bekerja.
Jebakan Pertama: Overflow Saat INSERT #
Melewati batas atas walau satu angka saja membuat server menolak nilai mentah-mentah. Kita coba menyisipkan barang dengan stok 32768 — tepat satu di atas batas smallint:
INSERT INTO barang (id, nama, stok, harga)
VALUES (4, 'Paku Payung', 32768, 500);Output yang Dihasilkan:
psql:overflow-smallint.sql:2: ERROR: smallint out of rangeError smallint out of range ini sebenarnya berita baik: data yang tidak muat tidak pernah tersimpan setengah-setengah, dan psql menghentikan eksekusi di titik itu. Tetapi kalau angka datang dari form pengguna atau file impor, satu baris buruk bisa menghentikan seluruh proses — validasi rentang di sisi aplikasi tetap perlu.
Jebakan Kedua: Overflow Saat Berhitung #
Jebakan yang lebih licik: kedua operand muat di tipe, tetapi hasil hitungannya tidak. Menambahkan 1 ke batas atas integer memicu error yang sama meskipun tidak ada INSERT sama sekali:
SELECT 2147483647::integer + 1 AS hasil;Output yang Dihasilkan:
psql:overflow-hitung.sql:1: ERROR: integer out of rangePenjumlahan dua integer dihitung sebagai integer juga — PostgreSQL tidak otomatis “naik kelas” ke bigint. Solusinya: naikkan salah satu operand secara eksplisit, misalnya 2147483647::integer + 1::bigint, agar seluruh perhitungan dikerjakan dalam 64 bit.
Pembagian Integer Membulatkan ke Bawah #
Perilaku yang paling sering menggigit pemula: membagi dua bilangan bulat menghasilkan bilangan bulat lagi, dengan pecahan dibuang — bukan dibulatkan. 7 / 2 adalah 3, bukan 3,5 dan bukan 4:
SELECT 7 / 2 AS hasil_bagi, 7 % 2 AS sisa;
SELECT nama, stok,
stok / 12 AS jumlah_lusin,
stok % 12 AS sisa_unit
FROM barang
ORDER BY id;Output yang Dihasilkan:
hasil_bagi | sisa
------------+------
3 | 1
(1 row)
nama | stok | jumlah_lusin | sisa_unit
---------------+-------+--------------+-----------
Buku Tulis | 120 | 10 | 0
Laptop Kantor | 15 | 1 | 3
Kertas HVS A4 | 32767 | 2730 | 7
(3 rows)Query kedua memakai perilaku ini untuk kebaikan: menghitung berapa lusin (kelompok dua belas) yang bisa dibentuk dari stok tiap barang. Operator % (modulo) melengkapi dengan sisa yang tidak cukup untuk satu lusin — Laptop Kantor punya 15 unit, artinya 1 lusin (15 / 12 = 1) dengan sisa 3 unit (15 % 12 = 3). Kalau hasil pecahan justru dibutuhkan, kedua operand perlu dinaikkan ke numeric terlebih dahulu, misalnya 7::numeric / 2 yang menghasilkan 3.5.
Kolom ID Otomatis: Identity dan Serial #
Tabel barang kita mengisi id secara manual, dan itu merepotkan. PostgreSQL punya dua mekanisme penomoran otomatis: serial (sintaks lama khas PostgreSQL) dan GENERATED AS IDENTITY (standar SQL, diperkenalkan PostgreSQL 10). Keduanya mengisi kolom integer dengan nomor berurutan secara otomatis:
CREATE TABLE pelanggan_baru (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
nama text NOT NULL
);
INSERT INTO pelanggan_baru (nama) VALUES ('Budi'), ('Siti');
SELECT id, nama
FROM pelanggan_baru
ORDER BY id;Output yang Dihasilkan:
CREATE TABLE
INSERT 0 2
id | nama
----+------
1 | Budi
2 | Siti
(2 rows)Dua INSERT di atas tidak menyebut kolom id sama sekali, namun baris Budi dan Siti menerima nomor 1 dan 2 dari identity column. Kata ALWAYS berarti nilai id hanya boleh diisi oleh sistem — upaya INSERT dengan id eksplisit akan ditolak. Untuk aplikasi baru, dokumentasi resmi merekomendasikan IDENTITY daripada serial; detail pengaturannya (mulai dari nomor berapa, kapan nomor dipakai ulang) kita bahas tuntas di section Desain Tabel.
Bilangan bulat menutupi hitungan dan rupiah utuh, tetapi banyak nilai dunia nyata memang berpecah: berat barang 2,5 kg, bunga pinjaman 3,75 persen, kurs 15.842,50. Ketika pecahan harus dihitung persis — terutama untuk uang — kita berpindah ke tipe numeric, topik chapter berikutnya.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
buat-tabel.sql
Unduh berkasCREATE TABLE barang (
id integer PRIMARY KEY,
nama text NOT NULL,
stok smallint NOT NULL DEFAULT 0,
harga integer NOT NULL
);
INSERT INTO barang (id, nama, stok, harga) VALUES
(1, 'Buku Tulis', 120, 4500),
(2, 'Laptop Kantor', 15, 8500000),
(3, 'Kertas HVS A4', 32767, 55000);
SELECT id, nama, stok, harga
FROM barang
ORDER BY id;
CREATE TABLE video (
id integer PRIMARY KEY,
judul text NOT NULL,
jumlah_tayangan bigint NOT NULL DEFAULT 0
);
INSERT INTO video (id, judul, jumlah_tayangan) VALUES
(1, 'Belajar PostgreSQL dari Nol', 3250000000),
(2, 'Tur Keliling Toko Maju Jaya', 1500000);
SELECT id, judul, jumlah_tayangan
FROM video
ORDER BY id;
identitas.sql
Unduh berkasCREATE TABLE pelanggan_baru (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
nama text NOT NULL
);
INSERT INTO pelanggan_baru (nama) VALUES ('Budi'), ('Siti');
SELECT id, nama
FROM pelanggan_baru
ORDER BY id;
overflow-hitung.sql
Unduh berkasSELECT 2147483647::integer + 1 AS hasil;
overflow-smallint.sql
Unduh berkasINSERT INTO barang (id, nama, stok, harga)
VALUES (4, 'Paku Payung', 32768, 500);
pembagian-bulat.sql
Unduh berkasSELECT 7 / 2 AS hasil_bagi, 7 % 2 AS sisa;
SELECT nama, stok,
stok / 12 AS jumlah_lusin,
stok % 12 AS sisa_unit
FROM barang
ORDER BY id;
rentang-batas.sql
Unduh berkasSELECT 32767::smallint AS batas_atas, (-32768)::smallint AS batas_bawah;
SELECT 2147483647::integer AS batas_atas, (-2147483648)::integer AS batas_bawah;
SELECT 9223372036854775807::bigint AS batas_atas, (-9223372036854775808)::bigint AS batas_bawah;