Kolom seperti subtotal = harga * jumlah sering disimpan secara manual atau dihitung ulang di aplikasi — dua-duanya rawan: lupa menghitung, dan nilainya melenceng dari kolom sumbernya. Generated columns menyelesaikannya di dalam tabel itu sendiri: nilainya dihitung engine SQLite dari kolom lain pada baris yang sama, setiap saat, tanpa pernah bisa tidak sinkron. Fitur ini hadir sejak SQLite 3.31.0 (Januari 2020).

Kolom yang Dihitung Otomatis #

Tabel detail_pesanan berikut menyerahkan perhitungan subtotal kepada engine SQLite:

sql
CREATE TABLE detail_pesanan (
  id INTEGER PRIMARY KEY,
  nama_produk TEXT NOT NULL,
  harga INTEGER NOT NULL,
  jumlah INTEGER NOT NULL,
  subtotal INTEGER GENERATED ALWAYS AS (harga * jumlah)
);

INSERT INTO detail_pesanan (id, nama_produk, harga, jumlah) VALUES
  (1, 'Kopi Gayo', 45000, 2),
  (2, 'Gula Aren', 25000, 1),
  (3, 'Teh Melati', 18000, 3);
sql
SELECT id, nama_produk, harga, jumlah, subtotal FROM detail_pesanan ORDER BY id;

Output yang Dihasilkan:

text
1|Kopi Gayo|45000|2|90000
2|Gula Aren|25000|1|25000
3|Teh Melati|18000|3|54000

Bedah singkat definisi kolom subtotal:

  1. GENERATED ALWAYS AS (harga * jumlah): ekspresi boleh membaca kolom lain pada baris yang sama, fungsi deterministik seperti upper() dan printf(), serta CASE — tetapi tidak boleh membaca kolom baris lain.
  2. Tanpa kata VIRTUAL/STORED: nilai bawaannya VIRTUAL — hasil dihitung setiap kali kolom dibaca, tidak menyimpan apa pun di dalam berkas database.

Perhatikan juga INSERT di atas: daftar kolomnya sengaja tidak memuat subtotal, dan memang tidak boleh — kolom hasil perhitungan tidak menerima nilai dari luar:

sql
INSERT INTO detail_pesanan (id, nama_produk, harga, jumlah, subtotal)
VALUES (4, 'Sirup Vanila', 30000, 2, 999999);

Output yang Dihasilkan:

text
Parse error: cannot INSERT into generated column "subtotal"

Perubahan pada kolom sumber otomatis terpakai ulang. Jumlah Gula Aren dinaikkan menjadi 4:

sql
UPDATE detail_pesanan SET jumlah = 4 WHERE id = 2;
sql
SELECT subtotal FROM detail_pesanan WHERE id = 2;

Output yang Dihasilkan:

text
100000

VIRTUAL versus STORED #

Menambahkan kata STORED membuat hasil perhitungan benar-benar disimpan di berkas database saat baris ditulis:

sql
CREATE TABLE harga_khusus (
  id INTEGER PRIMARY KEY,
  nama_produk TEXT NOT NULL,
  harga INTEGER NOT NULL,
  diskon_persen INTEGER NOT NULL,
  harga_akhir INTEGER GENERATED ALWAYS AS (harga * (100 - diskon_persen) / 100) STORED
);

INSERT INTO harga_khusus (id, nama_produk, harga, diskon_persen) VALUES
  (1, 'Kopi Gayo', 50000, 20),
  (2, 'Teh Melati', 20000, 15);
sql
SELECT id, nama_produk, harga, diskon_persen, harga_akhir FROM harga_khusus ORDER BY id;

Output yang Dihasilkan:

text
1|Kopi Gayo|50000|20|40000
2|Teh Melati|20000|15|17000
AspekVIRTUALSTORED
PenyimpananTidak menyimpan nilaiNilai ditulis ke berkas database
Saat dihitungSetiap kali dibacaSaat baris ditulis atau diubah
Kecepatan bacaSedikit kerja ekstra per barisCepat dibaca seperti kolom biasa
Biaya tulis dan ukuran berkasRinganLebih berat

Pilihan praktisnya: VIRTUAL untuk perhitungan ringan seperti perkalian dua kolom; STORED untuk perhitungan berat yang dibaca jauh lebih sering daripada ditulis.

Merujuk Kolom Generated Lain #

Ekspresi generated column boleh membaca generated column lain pada baris yang sama, dan STORED pun boleh merujuk VIRTUAL. Tabel berikut memakai dua lapis perhitungan:

sql
CREATE TABLE cek_stok (
  id INTEGER PRIMARY KEY,
  stok INTEGER NOT NULL,
  ambang INTEGER NOT NULL,
  kurang INTEGER GENERATED ALWAYS AS (stok - ambang) VIRTUAL,
  peringatan INTEGER GENERATED ALWAYS AS (CASE WHEN kurang < 0 THEN 1 ELSE 0 END) STORED
);

INSERT INTO cek_stok (id, stok, ambang) VALUES
  (1, 10, 15),
  (2, 20, 15);
sql
SELECT id, stok, ambang, kurang, peringatan FROM cek_stok ORDER BY id;

Output yang Dihasilkan:

text
1|10|15|-5|1
2|20|15|5|0

Kolom peringatan membaca kurang — hasil perhitungan stok - ambang — lalu memutuskan sendiri nilainya lewat CASE. Karena ekspresinya harus deterministik, hasil setiap baris selalu bisa direproduksi.

Larangan pada Generated Columns #

Ada empat larangan yang langsung ditolak engine SQLite saat CREATE TABLE. Generated column tidak boleh menjadi primary key:

sql
CREATE TABLE t_pk (
  a INTEGER,
  b INTEGER GENERATED ALWAYS AS (a + 1) PRIMARY KEY
);

Output yang Dihasilkan:

text
Parse error: generated columns cannot be part of the PRIMARY KEY

Subquery dilarang karena bisa membaca baris lain:

sql
CREATE TABLE t_sub (
  a INTEGER,
  b INTEGER GENERATED ALWAYS AS ((SELECT max(x) FROM t))
);

Output yang Dihasilkan:

text
Parse error: subqueries prohibited in generated columns

Fungsi non-deterministic dilarang karena hasilnya berubah-ubah:

sql
CREATE TABLE t_rand (
  a INTEGER,
  b INTEGER GENERATED ALWAYS AS (abs(random()))
);

Output yang Dihasilkan:

text
Parse error: non-deterministic functions prohibited in generated columns
    a INTEGER,   b INTEGER GENERATED ALWAYS AS (abs(random())) );
                                      error here ---^

DEFAULT juga tidak berlaku karena nilainya memang ditentukan ekspresi:

sql
CREATE TABLE t_def (
  a INTEGER,
  b INTEGER GENERATED ALWAYS AS (a + 1) DEFAULT 5
);

Output yang Dihasilkan:

text
Parse error: cannot use DEFAULT on a generated column

Menambah Kolom Generated pada Tabel Berisi Data #

ALTER TABLE ADD COLUMN menerima kolom VIRTUAL meskipun tabelnya sudah berisi baris:

sql
CREATE TABLE nota_baru (
  id INTEGER PRIMARY KEY,
  nama TEXT NOT NULL
);
INSERT INTO nota_baru VALUES (1, 'Nota A');
ALTER TABLE nota_baru ADD COLUMN total INTEGER GENERATED ALWAYS AS (id * 1000) VIRTUAL;
sql
SELECT * FROM nota_baru ORDER BY id;

Output yang Dihasilkan:

text
1|Nota A|1000

Kolom STORED menemui batasan di sini — SQLite menolak menambahkannya pada tabel yang sudah berisi data:

sql
ALTER TABLE nota_baru ADD COLUMN pajak INTEGER GENERATED ALWAYS AS (id / 10) STORED;

Output yang Dihasilkan:

text
Runtime error: cannot add a STORED column

Definisi generated columns terlihat utuh pada .schema:

text
sqlite> .schema detail_pesanan
CREATE TABLE detail_pesanan (
  id INTEGER PRIMARY KEY,
  nama_produk TEXT NOT NULL,
  harga INTEGER NOT NULL,
  jumlah INTEGER NOT NULL,
  subtotal INTEGER GENERATED ALWAYS AS (harga * jumlah)
);

Dengan generated columns, desain database tidak berhenti di struktur statis — tabel Anda kini membawa logika perhitungan yang selalu konsisten. Section Fungsi dan Fitur Lanjutan melanjutkan dengan fitur SQL SQLite yang lebih dalam: fungsi scalar, tanggal dan waktu, JSON, UPSERT, trigger, hingga full-text search.