Chapter INSERT
sudah memperkenalkan VALUES dengan tiga baris sekaligus — tetapi data nyata jarang sekecil itu: katalog produk pembuka toko, hasil impor CSV, isi keranjang belanja, log sensor per menit. Memasukkan data besar baris demi baris membuat aplikasi mengirim ratusan perintah terpisah ke server, dan hampir seluruh waktu habis di perjalanan bolak-balik jaringan. Chapter ini memperbesar skala teknik multi-row VALUES, lalu melampaui VALUES sama sekali dengan INSERT ... SELECT yang mengambil data langsung dari tabel lain — dua pola yang menjadi tulang punggung seeding data dan migrasi harian.
Delapan Baris dalam Satu Perintah #
Tabel produk berikut sengaja dibuat kosong supaya kita bisa mengisinya dari nol seperti saat toko baru dibuka:
CREATE TABLE produk (
id integer PRIMARY KEY,
nama text NOT NULL,
kategori text NOT NULL,
harga numeric(10, 2) NOT NULL CHECK (harga > 0),
stok integer NOT NULL DEFAULT 0
);Output yang Dihasilkan:
CREATE TABLEDelapan produk pembuka dimasukkan dalam satu perintah — perhatikan bahwa daftar kolom cukup ditulis sekali di depan, dan setiap baris nilai tetap dibungkus kurung sendiri:
INSERT INTO produk (id, nama, kategori, harga, stok) VALUES
(1, 'Buku Tulis', 'alat tulis', 4500, 120),
(2, 'Pulpen Hitam', 'alat tulis', 3000, 200),
(3, 'Penghapus Putih', 'alat tulis', 2000, 150),
(4, 'Kertas HVS A4', 'kertas', 55000, 40),
(5, 'Kertas Folio', 'kertas', 20000, 35),
(6, 'Amplop Coklat', 'kertas', 1500, 0),
(7, 'Stapler Besar', 'alat tulis', 24000, 25),
(8, 'Lakban Besar', 'alat tulis', 18000, 30);
SELECT id, nama, kategori, harga, stok FROM produk ORDER BY id;Output yang Dihasilkan:
INSERT 0 8
id | nama | kategori | harga | stok
----+-----------------+------------+----------+------
1 | Buku Tulis | alat tulis | 4500.00 | 120
2 | Pulpen Hitam | alat tulis | 3000.00 | 200
3 | Penghapus Putih | alat tulis | 2000.00 | 150
4 | Kertas HVS A4 | kertas | 55000.00 | 40
5 | Kertas Folio | kertas | 20000.00 | 35
6 | Amplop Coklat | kertas | 1500.00 | 0
7 | Stapler Besar | alat tulis | 24000.00 | 25
8 | Lakban Besar | alat tulis | 18000.00 | 30
(8 rows)Tag INSERT 0 8 melaporkan kedelapan baris masuk sekaligus, dan SELECT penutup memverifikasi seluruhnya. Dibanding delapan INSERT terpisah, satu perintah ini menghapus delapan kali bolak-balik jaringan menjadi satu, satu kali parsing perintah, dan satu unit kerja atomik: bila baris ketujuh gagal (misalnya id bentrok), kedelapan baris dibatalkan bersama — tidak ada setengah katalog tersimpan.
INSERT dari Tabel Lain dengan SELECT #
VALUES cocok untuk data yang kita tulis tangan, tetapi ketika sumbernya sudah berupa tabel — hasil import sementara, tabel staging, output proses ETL — menyalinnya lewat VALUES sama sekali tidak praktis. INSERT INTO ... SELECT membaca langsung dari sumbernya. Pertama kita siapkan tabel sumber berisi daftar harga dari supplier:
CREATE TABLE daftar_supplier (
kode_produk integer PRIMARY KEY,
nama_produk text NOT NULL,
jenis text NOT NULL,
harga_beli numeric(10, 2) NOT NULL
);
INSERT INTO daftar_supplier (kode_produk, nama_produk, jenis, harga_beli) VALUES
(101, 'Gunting Kertas', 'alat tulis', 12500),
(102, 'Penggaris 30 cm', 'alat tulis', 4000),
(103, 'Kertas Karton', 'kertas', 3500),
(104, 'Spidol Permanen', 'alat tulis', 6500);
INSERT INTO produk (id, nama, kategori, harga, stok)
SELECT kode_produk, nama_produk, jenis, harga_beli, 0
FROM daftar_supplier
WHERE jenis = 'alat tulis'
ORDER BY kode_produk;
SELECT id, nama, kategori, harga, stok FROM produk WHERE id > 100 ORDER BY id;Output yang Dihasilkan:
CREATE TABLE
INSERT 0 4
INSERT 0 3
id | nama | kategori | harga | stok
-----+-----------------+------------+----------+------
101 | Gunting Kertas | alat tulis | 12500.00 | 0
102 | Penggaris 30 cm | alat tulis | 4000.00 | 0
104 | Spidol Permanen | alat tulis | 6500.00 | 0
(3 rows)Mari kita bedah bagian pentingnya:
INSERT INTO produk (id, nama, ...)menetapkan kolom tujuan seperti biasa — lima kolom, sama seperti polaVALUES.SELECT kode_produk, nama_produk, jenis, harga_beli, 0menggantikanVALUES. Yang penting adalah posisi: kolom pertama hasil SELECT (kode_produk) mengisi kolom pertama daftar tujuan (id), dan seterusnya. Nama kolom tidak harus sama; urutannya yang dipertukarkan.- Angka
0di posisi kelima adalah literal tetap — setiap baris baru mendapat stok0karena kolom sumbernya tidak ada. Literal, ekspresi, dan function (misalnyanow()untuk kolom waktu impor) boleh dicampur bebas di daftar SELECT. WHERE jenis = 'alat tulis'memfilter sumber sebelum penyalinan: hanya produkalat tulisyang masuk, sehinggaKertas Kartontertinggal. TagINSERT 0 3mencatat tepat tiga baris tersalin — kecocokan dengan hasilSELECTpenutup.
Pola ini adalah bentuk paling ringkas untuk seeding dan migrasi: satu perintah membaca, memfilter, dan menulis, tanpa data pernah keluar dari server ke aplikasi lalu masuk lagi. ORDER BY kode_produk pada SELECT sumber tidak wajib secara fungsional, tetapi menjaga urutan penyisipan (dan urutan fisik penyimpanan) tetap terprediksi.
Preview: Kombinasi dengan ON CONFLICT #
Satu masalah mengintai di pola penyalinan: data yang sama bisa datang dua kali. Menjalankan ulang perintah impor di atas akan menabrak primary key id = 101 dan seluruh perintah gagal. Klausa ON CONFLICT mengubah tabrakan itu menjadi jalur yang aman:
INSERT INTO produk (id, nama, kategori, harga, stok) VALUES
(1, 'Buku Tulis', 'alat tulis', 4500, 120),
(2, 'Pulpen Hitam', 'alat tulis', 3000, 200),
(9, 'Binder 2 Ring', 'alat tulis', 27000, 15)
ON CONFLICT (id) DO NOTHING;
SELECT count(*) AS total_produk FROM produk;Output yang Dihasilkan:
INSERT 0 1
total_produk
--------------
12
(1 row)Dari tiga baris yang dikirim, tag INSERT 0 1 hanya melaporkan satu masuk: id = 1 dan id = 2 bentrok dengan data yang sudah ada dan dilewati diam-diam lewat DO NOTHING, sementara Binder 2 Ring (id = 9) tersimpan — total produk naik dari 11 ke 12. Perintah impor seperti ini disebut idempotent: menjalankannya sekali atau sepuluh kali menghasilkan keadaan akhir yang sama. Ini baris pengantar saja; cara meng-update baris bentrok alih-alih melewatkannya dibahas tuntas di chapter ON CONFLICT.
Kesalahan Umum: Jumlah Kolom Tidak Cocok #
Ketika baris nilai disalin-tempel dan diedit, jumlah nilai di dalam kurung gampang melenceng dari daftar kolom. PostgreSQL menolaknya sebelum menyentuh data:
INSERT INTO produk (id, nama, kategori, harga, stok)
VALUES (200, 'Kalkulator', 'alat tulis');Output yang Dihasilkan:
psql:insert-jumlah-kolom-error.sql:2: ERROR: INSERT has more target columns than expressions
LINE 1: INSERT INTO produk (id, nama, kategori, harga, stok)
^Pesan INSERT has more target columns than expressions memaknai ketidakseimbangan itu: lima kolom dituju, tetapi hanya tiga nilai diberikan. Kesalahan sebaliknya (nilai lebih banyak daripada kolom) ditolak dengan pesan kembarannya, INSERT has more expressions than target columns. Kedua kasus sama-sama ditolak mentah-mentah — tidak ada nilai yang “ditembak” ke kolom yang salah. Perbaikannya selalu menyamakan dua hal: jumlah item di daftar kolom setelah nama tabel, dan jumlah nilai di setiap kurung VALUES (atau jumlah kolom pada SELECT sumber).
Kita sekarang punya tiga senjata pemuatan data: VALUES multi-baris untuk data tulisan tangan, INSERT ... SELECT untuk salinan antar tabel, dan ON CONFLICT untuk impor yang bisa diulang. Pertanyaan tinggal satu ketika bentrokan terjadi: bagaimana kalau baris lama justru harus diperbarui dengan data yang baru datang — bukan dilewati? Jawabannya adalah upsert sungguhan, ON CONFLICT DO UPDATE, chapter berikutnya.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
buat-tabel.sql
Unduh berkasCREATE TABLE produk (
id integer PRIMARY KEY,
nama text NOT NULL,
kategori text NOT NULL,
harga numeric(10, 2) NOT NULL CHECK (harga > 0),
stok integer NOT NULL DEFAULT 0
);
insert-delapan-baris.sql
Unduh berkasINSERT INTO produk (id, nama, kategori, harga, stok) VALUES
(1, 'Buku Tulis', 'alat tulis', 4500, 120),
(2, 'Pulpen Hitam', 'alat tulis', 3000, 200),
(3, 'Penghapus Putih', 'alat tulis', 2000, 150),
(4, 'Kertas HVS A4', 'kertas', 55000, 40),
(5, 'Kertas Folio', 'kertas', 20000, 35),
(6, 'Amplop Coklat', 'kertas', 1500, 0),
(7, 'Stapler Besar', 'alat tulis', 24000, 25),
(8, 'Lakban Besar', 'alat tulis', 18000, 30);
SELECT id, nama, kategori, harga, stok FROM produk ORDER BY id;
insert-jumlah-kolom-error.sql
Unduh berkasINSERT INTO produk (id, nama, kategori, harga, stok)
VALUES (200, 'Kalkulator', 'alat tulis');
insert-on-conflict-preview.sql
Unduh berkasINSERT INTO produk (id, nama, kategori, harga, stok) VALUES
(1, 'Buku Tulis', 'alat tulis', 4500, 120),
(2, 'Pulpen Hitam', 'alat tulis', 3000, 200),
(9, 'Binder 2 Ring', 'alat tulis', 27000, 15)
ON CONFLICT (id) DO NOTHING;
SELECT count(*) AS total_produk FROM produk;
insert-select.sql
Unduh berkasCREATE TABLE daftar_supplier (
kode_produk integer PRIMARY KEY,
nama_produk text NOT NULL,
jenis text NOT NULL,
harga_beli numeric(10, 2) NOT NULL
);
INSERT INTO daftar_supplier (kode_produk, nama_produk, jenis, harga_beli) VALUES
(101, 'Gunting Kertas', 'alat tulis', 12500),
(102, 'Penggaris 30 cm', 'alat tulis', 4000),
(103, 'Kertas Karton', 'kertas', 3500),
(104, 'Spidol Permanen', 'alat tulis', 6500);
INSERT INTO produk (id, nama, kategori, harga, stok)
SELECT kode_produk, nama_produk, jenis, harga_beli, 0
FROM daftar_supplier
WHERE jenis = 'alat tulis'
ORDER BY kode_produk;
SELECT id, nama, kategori, harga, stok FROM produk WHERE id > 100 ORDER BY id;