Gabungkan ON DELETE SET NULL dan ON DELETE CASCADE dengan menetapkan masing-masing tindakan pada foreign key yang terpisah. Panduan ini menggunakan artikel sintetis untuk menunjukkan bagaimana menghapus koleksi dapat mempertahankan artikelnya, sementara menghapus artikel akan menghapus bagian-bagiannya.
Tugasnya adalah menetapkan perilaku tersebut, memverifikasi kedua jalur penghapusan dari Terminal Mac, dan mengulangi pemeriksaan dengan node-postgres. Contoh-contoh ini menggunakan foreign key biasa dan tabel sementara, sehingga setiap eksekusi memiliki baris sampelnya sendiri.
Relasi mana yang sebaiknya menggunakan setiap tindakan penghapusan?
Tindakan penghapusan berlaku pada foreign key di tabel yang merujuk. SET NULL mengosongkan referensi saat baris yang dirujuk dihapus; CASCADE menghapus baris yang merujuk. PostgreSQL menjelaskan bahwa relasi opsional cocok menggunakan SET NULL, sedangkan komponen yang bergantung cocok menggunakan CASCADE. Constraints
Dalam contoh ini, sebuah koleksi mengelompokkan artikel, tetapi artikel dapat ada tanpa koleksi. Sebuah bagian termasuk dalam satu artikel dan tidak memiliki fungsi mandiri. Ini adalah pilihan desain yang ditetapkan untuk contoh:
| Foreign key | Tindakan yang dipilih | Hasil yang diinginkan |
|---|---|---|
demo_articles.collection_id merujuk ke demo_collections.collection_id |
ON DELETE SET NULL |
Menghapus koleksi mempertahankan artikelnya tanpa referensi ke koleksi. |
demo_sections.article_id merujuk ke demo_articles.article_id |
ON DELETE CASCADE |
Menghapus artikel akan menghapus bagian-bagiannya. |
Perilaku yang dihasilkan bergantung pada titik awal penghapusan. Menghapus koleksi mengubah collection_id pada artikel; tindakan itu tidak menghapus artikel. Karena itu, ON DELETE CASCADE pada foreign key bagian tidak memiliki penghapusan artikel untuk ditanggapi. Menghapus artikelnya sendiri akan mengaktifkan cascade tersebut. Hal ini mengikuti definisi terdokumentasi untuk kedua tindakan. CREATE TABLE
Jangan gunakan dua foreign key yang saling bersaing pada relasi yang sama untuk menyatakan “kosongkan referensi dan hapus baris.” Tentukan apakah baris yang merujuk harus tetap ada, lalu pilih tindakan yang menerapkan keputusan tersebut.
Buat contoh lengkap di Mac Anda
Anda memerlukan server PostgreSQL yang sedang berjalan, database yang sudah ada dan dapat Anda akses, serta psql yang tersedia di Terminal. Perintah di bawah ini menggunakan database lokal yang sudah ada bernama postgres; ganti dengan nama database Anda jika perlu. Pemasangan dan penyiapan server tidak dibahas dalam panduan ini.
Simpan kode berikut sebagai file teks biasa bernama setup.sql di folder kerja. Ketiga tabel bersifat sementara: PostgreSQL menghapus tabel sementara saat sesinya berakhir. Semua tabel yang dirujuk dalam contoh ini juga bersifat sementara, karena PostgreSQL tidak mengizinkan foreign key antara tabel sementara dan permanen. CREATE TABLE
CREATE TEMP TABLE demo_collections (
collection_id integer PRIMARY KEY,
title text NOT NULL
);
CREATE TEMP TABLE demo_articles (
article_id integer PRIMARY KEY,
collection_id integer,
title text NOT NULL,
CONSTRAINT demo_articles_collection_fk
FOREIGN KEY (collection_id)
REFERENCES demo_collections (collection_id)
ON DELETE SET NULL
);
CREATE TEMP TABLE demo_sections (
section_id integer PRIMARY KEY,
article_id integer NOT NULL,
heading text NOT NULL,
CONSTRAINT demo_sections_article_fk
FOREIGN KEY (article_id)
REFERENCES demo_articles (article_id)
ON DELETE CASCADE
);
CREATE INDEX demo_articles_collection_idx
ON demo_articles (collection_id);
CREATE INDEX demo_sections_article_idx
ON demo_sections (article_id);
INSERT INTO demo_collections (collection_id, title) VALUES
(1, 'Layout Notes'),
(2, 'Color Notes');
INSERT INTO demo_articles (article_id, collection_id, title) VALUES
(101, 1, 'Grid Spacing'),
(102, 2, 'Color Pairings');
INSERT INTO demo_sections (section_id, article_id, heading) VALUES
(1001, 101, 'Overview'),
(1002, 101, 'Examples'),
(1003, 102, 'Overview');
Perhatikan bahwa collection_id mengizinkan nilai null, sedangkan article_id pada tabel bagian wajib diisi. Tindakan SET NULL tetap harus memenuhi batasan tabel lainnya. Indeks eksplisit mencakup kolom yang merujuk; PostgreSQL tidak otomatis membuat indeks tersebut saat foreign key dideklarasikan. Constraints
Verifikasi kedua jalur penghapusan dalam satu sesi
Simpan blok berikutnya sebagai verify.sql di samping setup.sql. Ini adalah skrip psql yang lengkap: \ir menyertakan file penyiapan dengan jalur relatif terhadap skrip verifikasi. Jalankan kedua file melalui skrip ini agar tabel sementara tetap berada dalam sesi yang sama. Opsi -f menjalankan sebuah file, dan ON_ERROR_STOP menghentikan pemrosesan skrip setelah terjadi kesalahan. psql
BEGIN;
\ir setup.sql
DELETE FROM demo_collections
WHERE collection_id = 1
RETURNING collection_id;
SELECT a.article_id,
a.title,
a.collection_id,
c.title AS collection_title,
count(s.section_id) AS section_count
FROM demo_articles AS a
LEFT JOIN demo_collections AS c
ON c.collection_id = a.collection_id
LEFT JOIN demo_sections AS s
ON s.article_id = a.article_id
GROUP BY a.article_id, a.title, a.collection_id, c.title
ORDER BY a.article_id;
DELETE FROM demo_articles
WHERE article_id = 101
RETURNING article_id;
SELECT section_id, article_id, heading
FROM demo_sections
ORDER BY section_id;
ROLLBACK;
Dari Terminal, pindah ke folder yang berisi file-file tersebut lalu jalankan:
psql -X -v ON_ERROR_STOP=1 -d postgres -f verify.sql
Hasil yang diharapkan dari kueri pertama berasal dari baris sampel:
| Artikel | ID koleksi setelah koleksi dihapus | Judul koleksi | Jumlah bagian |
|---|---|---|---|
101 |
NULL |
NULL |
2 |
102 |
2 |
Color Notes |
1 |
Artikel 101 tetap ada beserta dua bagiannya. Setelah penghapusan kedua, kueri bagian seharusnya hanya menampilkan bagian 1003, yang termasuk dalam artikel 102. Kedua pemeriksaan ini membedakan tindakan mempertahankan artikel dari menghapus artikel beserta baris yang bergantung padanya.
Kueri verifikasi sengaja menggunakan LEFT JOIN. PostgreSQL mempertahankan baris yang tidak memiliki pasangan dari tabel di sebelah kiri dan memberikan nilai null untuk kolom di sebelah kanan yang tidak memiliki pasangan. Inner join ke koleksi akan menghilangkan artikel 101 setelah referensinya menjadi null, sehingga artikel yang dipertahankan tidak terlihat dalam hasil kueri tersebut. Table Expressions
Klausa RETURNING melaporkan baris yang dihapus dari tabel sasaran. Klausa ini tidak memberikan daftar lengkap semua bagian yang dihapus oleh cascade; kueri bagian yang terpisah memverifikasi hasil tersebut. DELETE
ROLLBACK terakhir membatalkan perubahan dalam demonstrasi. PostgreSQL juga memperlakukan pernyataan tunggal sebagai transaksi, jadi transaksi eksplisit berguna untuk mengelompokkan penyiapan, penghapusan, pemeriksaan, dan pembersihan. Transactions
Jalankan pemeriksaan yang sama dengan node-postgres
Untuk versi JavaScript, biarkan setup.sql apa adanya. Gunakan Node.js 24 untuk contoh ini dan pasang paket pg yang didokumentasikan di folder kerja: node-postgres
npm install pg
Simpan program lengkap berikut sebagai verify.mjs di samping setup.sql. Program ini membaca data sintetis yang sama, memverifikasi bahwa penghapusan koleksi mempertahankan kedua artikel dan ketiga bagiannya, lalu memverifikasi bahwa penghapusan artikel hanya menghapus bagian yang cocok.
Program ini menggunakan satu klien yang terhubung sepanjang transaksi, sebagaimana diwajibkan oleh node-postgres. Nilai kueri diteruskan melalui antarmuka array parameter yang terdokumentasi menggunakan $1. Transactions, Queries
import pg from 'pg';
import { readFile } from 'node:fs/promises';
import assert from 'node:assert/strict';
const { Client } = pg;
const client = new Client();
const setup = await readFile(
new URL('./setup.sql', import.meta.url),
'utf8'
);
await client.connect();
try {
await client.query('BEGIN');
await client.query(setup);
const removed = await client.query(
'DELETE FROM demo_collections WHERE collection_id = $1 RETURNING collection_id',
[1]
);
assert.deepEqual(removed.rows, [{ collection_id: 1 }]);
const articles = await client.query(
'SELECT article_id, collection_id FROM demo_articles ORDER BY article_id'
);
assert.deepEqual(articles.rows, [
{ article_id: 101, collection_id: null },
{ article_id: 102, collection_id: 2 }
]);
const before = await client.query(
'SELECT section_id FROM demo_sections ORDER BY section_id'
);
assert.deepEqual(
before.rows.map(row => row.section_id),
[1001, 1002, 1003]
);
await client.query(
'DELETE FROM demo_articles WHERE article_id = $1',
[101]
);
const after = await client.query(
'SELECT section_id FROM demo_sections ORDER BY section_id'
);
assert.deepEqual(after.rows, [{ section_id: 1003 }]);
console.log('Expected SET NULL and CASCADE outcomes verified.');
await client.query('ROLLBACK');
} catch (error) {
try {
await client.query('ROLLBACK');
} catch (rollbackError) {
console.error('Rollback failed:', rollbackError.message);
}
throw error;
} finally {
await client.end();
}
Jalankan program tersebut terhadap database lokal yang sama:
PGDATABASE=postgres node verify.mjs
new Client() membaca variabel lingkungan koneksi PostgreSQL. Pertahankan pengaturan koneksi yang sudah diperlukan server lokal Anda; mengganti nama database saja tidak mengonfigurasi server lain. Connecting
Jika sebuah pernyataan gagal, program memasuki jalur rollback dan menampilkan kesalahan. Pesan sukses hanya muncul setelah semua pemeriksaan hasil lolos. Program ini sengaja melakukan rollback bahkan saat berhasil, sehingga demonstrasinya dapat diulang.
Sintaks JavaScript diperiksa selama persiapan. Lingkungan riset tidak memiliki executable PostgreSQL, jadi contoh database diperiksa berdasarkan dokumentasi dan tidak dijalankan; tabel di atas menunjukkan hasil yang diharapkan.
Apa yang berubah dengan foreign key komposit?
Contoh utama menggunakan foreign key satu kolom. Foreign key komposit menambahkan keputusan lain: apakah penghapusan harus mengosongkan semua kolom yang merujuk atau hanya kolom opsional? PostgreSQL 15 memperkenalkan daftar kolom untuk tindakan foreign key ON DELETE SET; versi sebelumnya memengaruhi semua kolom dalam foreign key. PostgreSQL 15 Release Notes
Sebagai contoh, referensi artikel komposit dapat berisi pengenal edisi yang wajib diisi dan pengenal artikel terkait yang opsional. Pada PostgreSQL 15 atau yang lebih baru, ON DELETE SET NULL (related_article_id) dapat mempertahankan pengenal edisi sambil mengosongkan referensi artikel opsional. Tanpa daftar kolom tersebut, SET NULL menyasar kedua kolom. Sintaks ini merupakan ekstensi PostgreSQL dan tersedia untuk ON DELETE, bukan ON UPDATE. CREATE TABLE
Pengosongan sebagian juga memerlukan aturan pencocokan yang kompatibel. Aturan bawaan MATCH SIMPLE mengizinkan foreign key komposit berisi nilai null; MATCH FULL menolak campuran nilai null dan non-null pada kolom yang merujuk. Karena itu, mempertahankan pengenal edisi yang wajib diisi sambil hanya mengosongkan pengenal artikel terkait tidak kompatibel dengan MATCH FULL. Constraints
Pada PostgreSQL 14, gunakan skema dengan referensi opsional satu kolom yang terpisah, seperti pada contoh utama, jika hal itu merepresentasikan relasinya dengan akurat. Jika relasi komposit harus dipertahankan, kosongkan referensi opsionalnya secara eksplisit sebelum menghapus baris yang dirujuk, menggunakan satu transaksi. Ini adalah urutan yang dikelola aplikasi, bukan sintaks daftar kolom yang tidak didukung.
Ada batasan terdokumentasi lainnya pada PostgreSQL 18: foreign key temporal yang menggunakan PERIOD tidak mendukung CASCADE atau SET NULL. Foreign key biasa yang ditunjukkan di sini merupakan cara yang didukung untuk menggunakan tindakan penghapusan tersebut saat cakupan durasi bukan bagian dari relasi. CREATE TABLE
Bagaimana mendiagnosis hasil yang tidak terduga?
Gunakan jalur penghapusan sebagai urutan diagnosis:
- Pastikan tabel yang merujuk. Periksa
\d demo_articlesdan\d demo_sectionsdalam sesi yang berisi tabel-tabel tersebut. Periksa tindakan foreign key yang sebenarnya alih-alih menyimpulkannya dari nama kolom. PostgreSQL mendokumentasikan\dsebagai cara untuk memeriksa detail tabel dan nama batasan. Modifying Tables - Periksa nullabilitas dan batasan lainnya. Jika penghapusan koleksi gagal, pastikan kolom yang akan dikosongkan mengizinkan nilai null dan batasan lainnya menerima keadaan baris yang dihasilkan.
SET NULLtidak melewati batasan tersebut. Constraints - Periksa setiap foreign key yang relevan. Relasi tambahan mungkin memiliki tindakan berbeda. Tindakan yang tidak ditentukan akan menggunakan bawaan
NO ACTION, yang dapat mencegah penghapusan saat referensi masih ada. CREATE TABLE - Kueri tabel dasar sebelum menafsirkan join. Jika artikel masih ada di sana, periksa join dan filter yang mungkin menghilangkan referensi null barunya. Table Expressions
- Jalankan pernyataan transaksi pada satu klien. Dalam aplikasi yang menggunakan pool, gunakan satu klien untuk seluruh transaksi. node-postgres secara tegas menyarankan agar tidak menggunakan
pool.query()untuk pernyataan transaksi. Transactions
Untuk model artikel ini, uji penerimaannya konkret: menghapus koleksi 1 mempertahankan artikel 101 beserta bagian-bagiannya, sedangkan menghapus artikel 101 menyisakan hanya bagian milik artikel 102. Pertahankan kedua pemeriksaan ini saat mengadaptasi skema agar perilaku yang diinginkan terkait keberlangsungan dan penghapusan tetap terlihat.
Sumber
- Constraints — PostgreSQL Global Development Group
- CREATE TABLE — PostgreSQL Global Development Group
- psql — PostgreSQL Global Development Group
- Table Expressions — PostgreSQL Global Development Group
- DELETE — PostgreSQL Global Development Group
- Transactions — PostgreSQL Global Development Group
- PostgreSQL 15 Release Notes — PostgreSQL Global Development Group
- Modifying Tables — PostgreSQL Global Development Group
- node-postgres — node-postgres
- Transactions — node-postgres
- Queries — node-postgres
- Connecting — node-postgres