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:

TipePenyimpananRentangAlias
smallint2 byte-32.768 sampai 32.767int2
integer4 byte-2.147.483.648 sampai 2.147.483.647int, int4
bigint8 byte-9.223.372.036.854.775.808 sampai 9.223.372.036.854.775.807int8

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:

sql buat-tabel.sql
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:

text
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:

  1. 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 diisi 32767 — tepat di batas atas smallint — untuk membuktikan nilai itu masih sah.
  2. harga integer NOT NULL: harga rupiah ditulis tanpa pecahan, dan integer menampung hingga Rp2,1 miliar per barang — lebih dari cukup untuk isi toko. Barang senilai di atasnya (properti, kendaraan berat) sebaiknya pindah ke bigint.
  3. jumlah_tayangan bigint NOT NULL DEFAULT 0: angka 3250000000 (3,25 miliar) sengaja dipilih melewati batas integer; hanya bigint yang 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:

text
 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:

text
psql:overflow-smallint.sql:2: ERROR:  smallint out of range

Error 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:

text
psql:overflow-hitung.sql:1: ERROR:  integer out of range

Penjumlahan 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:

text
 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:

sql identitas.sql
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:

text
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 berkas
sql
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;

identitas.sql

Unduh berkas
sql
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;

overflow-hitung.sql

Unduh berkas
sql
SELECT 2147483647::integer + 1 AS hasil;

overflow-smallint.sql

Unduh berkas
sql
INSERT INTO barang (id, nama, stok, harga)
VALUES (4, 'Paku Payung', 32768, 500);

pembagian-bulat.sql

Unduh berkas
sql
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;

rentang-batas.sql

Unduh berkas
sql
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;