Nama pelanggan, alamat, catatan pesanan — mayoritas kolom di aplikasi dunia nyata menyimpan teks, dan PostgreSQL menyediakan tiga tipe untuknya: char(n), varchar(n), dan text. Warisan database lain membuat banyak orang memilih dengan asumsi yang keliru: bahwa char lebih cepat untuk kode pendek, atau varchar lebih hemat dari text. Kenyataannya di PostgreSQL jauh lebih membosankan sekaligus membebaskan — ketiganya disimpan dengan struktur yang sama dan sama cepatnya, sehingga pilihan sebenarnya hanya soal perilaku, bukan performa. Chapter ini membuktikannya sambil membedah tiga perilaku khas tiap tipe: spasi pengisi milik char, batas panjang milik varchar, dan kebebasan tanpa batas milik text.

Tiga Cara Menyimpan Teks #

Ketiga tipe ini adalah satu keluarga dengan satu perbedaan utama — apa yang terjadi pada panjang nilai:

TipeBatas panjangPerilaku terhadap panjang
char(n)Tetap tepat n karakter (1-10485760)Nilai lebih pendek diisi spasi sampai n; nilai lebih panjang ditolak
varchar(n)Maksimal n karakter (1-10485760)Nilai disimpan apa adanya; nilai lebih panjang dari n ditolak
textTanpa batas (sampai 1 GB per nilai)Nilai disimpan apa adanya

Angka n pada char(n) dan varchar(n) menghitung karakter, bukan byte — huruf beraksen seperti é dihitung satu karakter meski memakan dua byte dalam UTF-8. char tanpa (n) setara char(1); varchar tanpa (n) setara text.

Membuat Tabel dengan char, varchar, dan text #

Tabel keanggotaan berikut memakai ketiganya sekaligus dalam peran yang wajar: char(5) untuk kode berformat tetap, varchar(20) untuk nama yang wajar dibatasi, dan text untuk alamat yang panjangnya tidak terduga:

sql buat-tabel.sql
CREATE TABLE anggota (
    id     integer PRIMARY KEY,
    kode   char(5) NOT NULL,
    nama   varchar(20) NOT NULL,
    alamat text
);

INSERT INTO anggota (id, kode, nama, alamat) VALUES
    (1, 'ID001', 'Budi', 'Jl. Merdeka No. 10, Jakarta'),
    (2, 'ID002', 'Siti', 'Jl. Asia Afrika No. 5, Bandung'),
    (3, 'ID003', 'Toko Maju Jaya', 'Jl. Sudirman Kav. 52, Jakarta');

SELECT id, kode, length(kode) AS panjang_kode, nama, alamat
FROM anggota
ORDER BY id;

Output yang Dihasilkan:

text
CREATE TABLE
INSERT 0 3
 id | kode  | panjang_kode |      nama      |             alamat             
----+-------+--------------+----------------+--------------------------------
  1 | ID001 |            5 | Budi           | Jl. Merdeka No. 10, Jakarta
  2 | ID002 |            5 | Siti           | Jl. Asia Afrika No. 5, Bandung
  3 | ID003 |            5 | Toko Maju Jaya | Jl. Sudirman Kav. 52, Jakarta
(3 rows)

Ketiga kode anggota tepat lima karakter, jadi kolom char(5) menyimpannya tanpa perubahan dan length(kode) melaporkan 5 untuk semua baris. Nama terpanjang (“Toko Maju Jaya”, 14 karakter) masih longgar di bawah batas 20 milik varchar(20), dan alamat terpanjang disimpan utuh oleh text tanpa perlu kita berpikir panjang.

char(n) Menambah Spasi Pengisi #

Perilaku khas char(n) baru terlihat saat nilainya lebih pendek dari n. SQL standar memerintahkan char(n) mengisi kekurangan panjang dengan spasi, dan PostgreSQL mematuhinya — spasi itu benar-benar tersimpan di disk:

SELECT 'ID'::char(5) AS kode_char,
       length('ID'::char(5)) AS jumlah_karakter,
       octet_length('ID'::char(5)) AS jumlah_byte;

SELECT 'ID'::varchar(5) AS kode_varchar,
       length('ID'::varchar(5)) AS jumlah_karakter,
       octet_length('ID'::varchar(5)) AS jumlah_byte;

SELECT 'ID'::char(5) = 'ID' AS dibanding_tanpa_spasi;

Output yang Dihasilkan:

text
 kode_char | jumlah_karakter | jumlah_byte 
-----------+-----------------+-------------
 ID        |               2 |           5
(1 row)

 kode_varchar | jumlah_karakter | jumlah_byte 
--------------+-----------------+-------------
 ID           |               2 |           2
(1 row)

 dibanding_tanpa_spasi 
-----------------------
 t
(1 row)

Mari kita bedah hasil yang menarik ini:

  1. Baris pertama: 'ID'::char(5) hanya punya dua karakter berarti, tetapi octet_length melaporkan 5 — tiga spasi pengisi ikut tersimpan. Fungsi length sendiri melaporkan 2 karena untuk tipe char, fungsi pengukur karakter mengabaikan spasi tambahan di belakang.
  2. Baris kedua: varchar(5) menyimpan 'ID' apa adanya — dua karakter, dua byte, tidak ada spasi pengisi di mana pun.
  3. Baris ketiga: dalam perbandingan char dengan char, spasi belakang dianggap tidak signifikan — 'ID'::char(5) dianggap sama dengan 'ID', karena keduanya dibandingkan sebagai char dengan aturan SQL.

Jadi spasi pengisi char(n) itu nyata di penyimpanan tetapi hampir tak terlihat di permukaan: tampilan menyembunyikannya (psql memotong spasi belakang saat menampilkan char), length mengabaikannya, dan perbandingan mengampuninya. Satu-satunya tempat ia menggigit adalah saat nilainya keluar dari dunia char — misalnya ditempel ke text — karena cast char ke text membuang spasi pengisinya, atau saat menghitung ukuran baris: tabel besar dengan char(50) membayar penuh 50 karakter untuk setiap baris pendek.

varchar(n) Menolak Teks yang Terlalu Panjang #

Batas n pada varchar ditegakkan sungguhan. Nama anggota keempat kita, “Alexander Wolfgang Gunawan”, terdiri dari 26 karakter — melewati batas 20 milik varchar(20):

INSERT INTO anggota (id, kode, nama, alamat)
VALUES (4, 'ID004', 'Alexander Wolfgang Gunawan', 'Solo');

Output yang Dihasilkan:

text
psql:varchar-overflow.sql:2: ERROR:  value too long for type character varying(20)

Error value too long for type character varying(20) menyebutkan tipe dan batasnya dengan jelas, dan seluruh baris ditolak — kolom lain yang sah ikut tidak tersimpan. Batas seperti ini berguna sebagai penjaga integritas (nama kolom email jarang lebih dari 100 karakter), tetapi pilih n dengan longgar: memperbesar batas di kemudian hari lewat ALTER TABLE mudah, sedangkan baris yang sudah ditolak aplikasi tidak kembali dengan sendirinya.

Cast Eksplisit Memotong Diam-diam #

Ada satu celah di mana teks terlalu panjang justru lolos: cast eksplisit ke char(n) atau varchar(n) tidak menolak, melainkan memotong nilai sampai pas n karakter:

sql potong-cast.sql
SELECT 'Budi Santoso'::char(4) AS potong_char,
       'Budi Santoso'::varchar(4) AS potong_varchar;

SELECT length('Budi Santoso'::char(4)) AS panjang_char,
       length('Budi Santoso'::varchar(4)) AS panjang_varchar;

Output yang Dihasilkan:

text
 potong_char | potong_varchar 
-------------+----------------
 Budi        | Budi
(1 row)

 panjang_char | panjang_varchar 
--------------+-----------------
            4 |               4
(1 row)

Dua belas karakter masuk, empat karakter keluar: 'Budi Santoso' menjadi 'Budi' tanpa error, tanpa warning, tanpa sisa. Perilaku potong pada cast eksplisit ini memang disediakan standar SQL untuk keperluan pemotongan yang sengaja diinginkan — tapi kebalikannya berbahaya: CAST(nama AS varchar(20)) di kode aplikasi bisa memotong nama pelanggan tanpa pernah terdengar. INSERT ke kolom tetap menolak; hanya cast eksplisit yang memotong.

Mitos Performa: Ketiganya Sama Cepat #

Mitos paling awet di dunia SQL: “char lebih cepat untuk nilai pendek” atau “text lebih lambat dari varchar”. Di PostgreSQL, itu semua tidak benar — dokumentasi resminya menegaskan bahwa ketiganya memakai struktur penyimpanan yang persis sama, dan tidak ada perbedaan performa yang terukur:

Klaim yang beredarKenyataan di PostgreSQL
char(n) lebih cepat untuk kode pendekJustru menambah spasi pengisi yang ikut tersimpan dan dibandingkan
varchar lebih cepat dari text pada nilai panjangIdentik — varchar tanpa batas adalah text
text boros untuk nilai pendekStruktur penyimpanan sama; panjang nilai yang menentukan

Alasan historisnya: beberapa database lawas memang menyimpan char dan varchar dengan format berbeda. PostgreSQL tidak — sejak lama ketiganya memakai varlena, struktur panjang-variabel yang sama, sehingga n pada varchar(n) murni berfungsi sebagai constraint, bukan optimasi.

Teks selesai disimpan apa adanya, sekarang kita berpindah ke tipe paling ringkas di katalog: boolean — dua nilai, tiga kemungkinan, dan jebakan logikanya yang khas.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

buat-tabel.sql

Unduh berkas
sql
CREATE TABLE anggota (
    id     integer PRIMARY KEY,
    kode   char(5) NOT NULL,
    nama   varchar(20) NOT NULL,
    alamat text
);

INSERT INTO anggota (id, kode, nama, alamat) VALUES
    (1, 'ID001', 'Budi', 'Jl. Merdeka No. 10, Jakarta'),
    (2, 'ID002', 'Siti', 'Jl. Asia Afrika No. 5, Bandung'),
    (3, 'ID003', 'Toko Maju Jaya', 'Jl. Sudirman Kav. 52, Jakarta');

SELECT id, kode, length(kode) AS panjang_kode, nama, alamat
FROM anggota
ORDER BY id;

padding-char.sql

Unduh berkas
sql
SELECT 'ID'::char(5) AS kode_char,
       length('ID'::char(5)) AS jumlah_karakter,
       octet_length('ID'::char(5)) AS jumlah_byte;

SELECT 'ID'::varchar(5) AS kode_varchar,
       length('ID'::varchar(5)) AS jumlah_karakter,
       octet_length('ID'::varchar(5)) AS jumlah_byte;

SELECT 'ID'::char(5) = 'ID' AS dibanding_tanpa_spasi;

potong-cast.sql

Unduh berkas
sql
SELECT 'Budi Santoso'::char(4) AS potong_char,
       'Budi Santoso'::varchar(4) AS potong_varchar;

SELECT length('Budi Santoso'::char(4)) AS panjang_char,
       length('Budi Santoso'::varchar(4)) AS panjang_varchar;

varchar-overflow.sql

Unduh berkas
sql
INSERT INTO anggota (id, kode, nama, alamat)
VALUES (4, 'ID004', 'Alexander Wolfgang Gunawan', 'Solo');