Oracle
Terbukti di Oracle sungguhan
21 skrip yang menerjemahkan konsep kuliah menjadi objek Oracle — tiga basis data, database link, partisi, replikasi, two-phase commit, sampai transaksi ragu-ragu. Semuanya dibangkitkan dari mesin yang sama dengan lab, lalu dijalankan otomatis pada Oracle sungguhan dan memeriksa hasilnya sendiri.
Oracle AI Database 26ai Free Release 23.26.3.0.0 - Develop, Learn, and Run for Free Version 23.26.3.0.0 · image gvenzl/oracle-free:23-slim · diuji 2026-09-17 · 69 detik. Log SQL*Plus lengkap setiap skrip ada di folder oracle/bukti.
Topologi uji
Setiap situs adalah pluggable database terpisah dengan kamus data, pengguna, dan catatan transaksinya sendiri. Di produksi ketiganya berada di server berbeda; di sini ketiganya berbagi satu container agar bisa diulang di laptop — database link, 2PC, dan DBA_2PC_PENDING tetap sungguhan.
Yang hanya ketahuan setelah dijalankan di Oracle
| Temuan | Gejala di Oracle | Perbaikan |
|---|---|---|
| Reference partitioning butuh ROW MOVEMENT | ORA-14661 saat tabel anak dibuat, karena induknya sudah ENABLE ROW MOVEMENT | Tabel anak ikut ENABLE ROW MOVEMENT |
| Catatan in-doubt ditulis asinkron | Tepat setelah ORA-02054, DBA_2PC_PENDING masih kosong; baris baru muncul beberapa detik kemudian | Skrip menunggu dengan polling sebelum membaca ID transaksi |
| Kueri lewat link membuka transaksi | ORA-02043: COMMIT FORCE ditolak karena SELECT ke dba_2pc_pending@SITUS_BANDUNG belum diakhiri | COMMIT sebelum COMMIT FORCE |
| Membersihkan catatan 2PC butuh hak SYS | DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY gagal sebagai pemilik skema | Dipindah ke langkah 14d sebagai SYS |
| Oracle memeriksa kolom saat parse | Kolom salah pada tabel kosong tetap ORA-00904/ORA-00918/ORA-00979; mesin terminal lama meloloskannya | Validasi semantik statis di mesin SQL |
| SELECT atas baris terkunci in-doubt | ORA-01591 — pembaca pun ditolak, bukan melihat data lama | Terminal menolak baca/tulis tabel yang dikunci transaksi ragu-ragu |
| TRUNCATE, CHECK kolom, dan format tanggal | TRUNCATE induk lolos bila anak kosong; CHECK kolom yang menyebut kolom lain ORA-02438; 31/12/2025 → ORA-01830 | Mesin terminal meniru ketiganya persis |
39 kode galat: Oracle vs Terminal SQL
Perintah yang sama dijalankan di Oracle dan di terminal ORACLEDECK. Tekan Coba untuk menjalankannya sendiri di terminal.
| Arti | Oracle | Terminal | |
|---|---|---|---|
nilai PRIMARY KEY sudah dipakaiINSERT INTO g01 VALUES (1) | ORA-00001 | ORA-00001 | |
nilai UNIQUE sudah dipakaiINSERT INTO g02 VALUES (2, 'A1') | ORA-00001 | ORA-00001 | |
kolom NOT NULL tidak diisiINSERT INTO g03 (id) VALUES (1) | ORA-01400 | ORA-01400 | |
teks melebihi panjang VARCHAR2INSERT INTO g04 VALUES ('abcd') | ORA-12899 | ORA-12899 | |
angka melebihi presisi NUMBER(p,s)INSERT INTO g05 VALUES (1234) | ORA-01438 | ORA-01438 | |
teks dimasukkan ke kolom NUMBERINSERT INTO g06 VALUES ('abc') | ORA-01722 | ORA-01722 | |
kendala CHECK dilanggarINSERT INTO g07 VALUES ('X') | ORA-02290 | ORA-02290 | |
nilai FOREIGN KEY tidak ada di tabel indukINSERT INTO g08b VALUES (1, 9) | ORA-02291 | ORA-02291 | |
baris induk masih dirujuk (tanpa CASCADE)DELETE FROM g09a | ORA-02292 | ORA-02292 | |
tabel atau view tidak adaSELECT * FROM g10_tidak_ada | ORA-00942 | ORA-00942 | |
kolom tidak dikenalSELECT b FROM g11 | ORA-00904 | ORA-00904 | |
nama kolom ada di dua tabel yang di-joinSELECT id FROM g12a JOIN g12b ON g12a.id = g12b.id | ORA-00918 | ORA-00918 | |
nama objek sudah dipakaiCREATE TABLE g13 (b NUMBER(4)) | ORA-00955 | ORA-00955 | |
nama kolom gandaCREATE TABLE g14 (a NUMBER(4), a NUMBER(4)) | ORA-00957 | ORA-00957 | |
tabel induk masih dirujuk kunci asingDROP TABLE g15a | ORA-02449 | ORA-02449 | |
TRUNCATE tabel induk yang anaknya masih berisi barisTRUNCATE TABLE g16a | ORA-02266 | ORA-02266 | |
kolom SELECT tidak ikut GROUP BYSELECT a, b FROM g17 GROUP BY a | ORA-00979 | ORA-00979 | |
kolom biasa bercampur fungsi agregat tanpa GROUP BYSELECT a, COUNT(*) FROM g18 | ORA-00937 | ORA-00937 | |
fungsi agregat di WHERE (seharusnya HAVING)SELECT a FROM g29 WHERE COUNT(*) > 1 | ORA-00934 | ORA-00934 | |
daftar nama kolom view tidak sama dengan kolom kueriCREATE VIEW g19v (x, y) AS SELECT a FROM g19 | ORA-01730 | ORA-01730 | |
UNIQUE INDEX pada data yang sudah gandaCREATE UNIQUE INDEX g20i ON g20 (a) | ORA-01452 | ORA-01452 | |
DML pada view gabungan UNION ALLDELETE FROM g21v | ORA-01732 | ORA-01732 | |
kunci asing merujuk kolom yang bukan PRIMARY KEY/UNIQUECREATE TABLE g22b (x VARCHAR2(10) REFERENCES g22a (nama)) | ORA-02270 | ORA-02270 | |
tabel induk tidak punya PRIMARY KEYCREATE TABLE g23b (x NUMBER(4) REFERENCES g23a) | ORA-02268 | ORA-02268 | |
jumlah kolom kunci asing tidak sama dengan kunci indukCREATE TABLE g24b (x NUMBER(4), FOREIGN KEY (x) REFERENCES g24a (a, b)) | ORA-02256 | ORA-02256 | |
VARCHAR2 tanpa panjangCREATE TABLE g25 (a VARCHAR2) | ORA-00906 | ORA-00906 | |
CHECK tingkat kolom menyebut kolom lainCREATE TABLE g26 (a NUMBER(4), b NUMBER(4) CHECK (a > 1)) | ORA-02438 | ORA-02438 | |
CHECK tingkat tabel menyebut kolom yang tidak adaCREATE TABLE g26b (a NUMBER(4), CHECK (b > 1)) | ORA-00904 | ORA-00904 | |
ROLLBACK TO savepoint yang belum dibuatROLLBACK TO SAVEPOINT g27_tidak_ada | ORA-01086 | ORA-01086 | |
DROP INDEX yang tidak adaDROP INDEX g28_tidak_ada | ORA-01418 | ORA-01418 | |
COMMIT FORCE untuk ID transaksi yang tidak ragu-raguCOMMIT FORCE '9.99.999' | ORA-02058 | ORA-02058 | |
fungsi tidak dikenalSELECT fungsi_tidak_ada(1) FROM dual | ORA-00904 | ORA-00904 | |
tanggal gaya DD/MM/YYYY pada format YYYY-MM-DD: masih ada sisa inputINSERT INTO g30 VALUES ('31/12/2025') | ORA-01830 | ORA-01830 | |
bulan 13INSERT INTO g32 VALUES ('2025-13-01') | ORA-01843 | ORA-01843 | |
hari 32INSERT INTO g33 VALUES ('2025-01-32') | ORA-01847 | ORA-01847 | |
29 Februari pada tahun bukan kabisatINSERT INTO g34 VALUES ('2025-02-29') | ORA-01839 | ORA-01839 | |
tanggal tanpa hariINSERT INTO g35 VALUES ('2025-09') | ORA-01840 | ORA-01840 | |
teks yang bukan tanggalINSERT INTO g36 VALUES ('kemarin') | ORA-01841 | ORA-01841 | |
DESC objek yang tidak adaDESC g31_tidak_ada | ORA-04043 | ORA-04043 |
Hasil lengkap termasuk 53 kueri, 7 skrip DML, dan 4 skrip \ekspor: VERIFIKASI_MESIN.md.
Pemetaan konsep ke fitur Oracle
| Konsep kuliah | Fitur Oracle | Skrip |
|---|---|---|
| Tiga situs | Pluggable database + database link | 01, 02, 10 |
| Fragmentasi horizontal primer | ALTER TABLE ... MODIFY PARTITION BY LIST ... ONLINE | 06 |
| Fragmentasi horizontal turunan | PARTITION BY REFERENCE | 07 |
| Fragmentasi vertikal | Tabel terpisah + VIEW perekat atas kunci | 08 |
| Alokasi fragmen | Tablespace per situs; fragmen di PDB situsnya | 03, 09 |
| Transparansi lokasi | SYNONYM + VIEW UNION ALL | 10, 15 |
| Replikasi asinkron | MV log di sumber + REFRESH FAST ON DEMAND di replika | 11, 12 |
| Two-phase commit | COMMIT atas dua basis data | 13 |
| Transaksi ragu-ragu | ORA-2PC-CRASH-TEST-7, DBA_2PC_PENDING, COMMIT FORCE | 14a–14d |
| Reduksi lokalisasi | Partition pruning: PARTITION LIST SINGLE | 07, 16 |
| Join terdistribusi | Operasi REMOTE pada rencana eksekusi | 16 |
21 skrip
01_situs_pdb.sql LULUS · 1 cek cdb · sys
- LULUS: PDB BANDUNG dan SURABAYA terbuka READ WRITE
Log SQL*Plus: 01_situs_pdb.log
-- ==========================================================================
-- Langkah 1 - Tiga situs = tiga basis data
-- Situs Jakarta memakai PDB bawaan; Bandung dan Surabaya dibuat sebagai PDB baru.
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=cdb sebagai=sys
-- Sambungan: SYS AS SYSDBA ke CDB$ROOT.
-- Oracle Free 23ai: PDB bawaan FREEPDB1. Oracle XE 21c: XEPDB1 (XE mengizinkan 3 PDB).
-- Di produksi, tiap situs adalah server terpisah; PDB dipakai agar bisa diuji di satu laptop
-- dengan database link, 2PC, dan DBA_2PC_PENDING yang sungguhan.
-- Situs S2 - Bandung
CREATE PLUGGABLE DATABASE BANDUNG ADMIN USER PDB_ADMIN IDENTIFIED BY "&&sandi_rs_app"
FILE_NAME_CONVERT = ('/pdbseed/', '/BANDUNG/');
ALTER PLUGGABLE DATABASE BANDUNG OPEN;
ALTER PLUGGABLE DATABASE BANDUNG SAVE STATE;
-- Situs S3 - Surabaya
CREATE PLUGGABLE DATABASE SURABAYA ADMIN USER PDB_ADMIN IDENTIFIED BY "&&sandi_rs_app"
FILE_NAME_CONVERT = ('/pdbseed/', '/SURABAYA/');
ALTER PLUGGABLE DATABASE SURABAYA OPEN;
ALTER PLUGGABLE DATABASE SURABAYA SAVE STATE;
SELECT CASE WHEN (SELECT COUNT(*) FROM v$pdbs WHERE name IN ('BANDUNG', 'SURABAYA') AND open_mode = 'READ WRITE') = 2 THEN 'LULUS: PDB BANDUNG dan SURABAYA terbuka READ WRITE' ELSE 'GAGAL: PDB BANDUNG dan SURABAYA terbuka READ WRITE' END AS cek FROM dual;
02_pengguna_situs.sql LULUS · 3 cek jakarta, bandung, surabaya · sys
- LULUS: pengguna RS_APP dan tablespace situs siap
- LULUS: pengguna RS_APP dan tablespace situs siap
- LULUS: pengguna RS_APP dan tablespace situs siap
Log SQL*Plus: 02_pengguna_situs.jakarta.log · 02_pengguna_situs.bandung.log · 02_pengguna_situs.surabaya.log
-- ==========================================================================
-- Langkah 2 - Pemilik skema aplikasi di setiap situs
-- Dijalankan tiga kali: di JAKARTA, BANDUNG, dan SURABAYA.
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=jakarta,bandung,surabaya sebagai=sys
-- Sambungan: SYS AS SYSDBA ke PDB situs JAKARTA,BANDUNG,SURABAYA.
-- Variabel &&situs diisi nama situs (JAKARTA/BANDUNG/SURABAYA), &&dir_data folder data Oracle.
-- Berkas data dikelola Oracle (OMF) agar nama berkas tidak perlu ditulis manual:
ALTER SYSTEM SET db_create_file_dest = '&&dir_data' SCOPE = BOTH;
CREATE TABLESPACE TS_&&situs DATAFILE SIZE 50M AUTOEXTEND ON NEXT 10M MAXSIZE 1G;
CREATE USER RS_APP IDENTIFIED BY "&&sandi_rs_app"
DEFAULT TABLESPACE TS_&&situs QUOTA UNLIMITED ON TS_&&situs;
GRANT CREATE SESSION, CREATE TABLE, CREATE VIEW, CREATE SYNONYM,
CREATE DATABASE LINK, CREATE MATERIALIZED VIEW, CREATE PROCEDURE TO RS_APP;
-- Hak untuk memeriksa dan menyelesaikan transaksi terdistribusi (Langkah 13):
GRANT SELECT_CATALOG_ROLE, FORCE ANY TRANSACTION TO RS_APP;
GRANT SELECT ON dba_2pc_pending TO RS_APP;
GRANT SELECT ON dba_2pc_neighbors TO RS_APP;
GRANT EXECUTE ON dbms_transaction TO RS_APP;
SELECT CASE WHEN (SELECT COUNT(*) FROM dba_users WHERE username = 'RS_APP') = 1 AND (SELECT COUNT(*) FROM dba_tablespaces WHERE tablespace_name = 'TS_&&situs') = 1 THEN 'LULUS: pengguna RS_APP dan tablespace situs siap' ELSE 'GAGAL: pengguna RS_APP dan tablespace situs siap' END AS cek FROM dual;
03_tablespace_alokasi.sql LULUS · 1 cek jakarta · sys
- LULUS: tiga tablespace alokasi tersedia di situs pusat
Log SQL*Plus: 03_tablespace_alokasi.log
-- ==========================================================================
-- Langkah 3 - Alokasi di dalam satu basis data: satu tablespace per situs
-- Dipakai Langkah 6: partisi PASIEN ditaruh di tablespace situsnya.
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=jakarta sebagai=sys
-- Sambungan: SYS AS SYSDBA ke PDB situs JAKARTA.
CREATE TABLESPACE TS_BANDUNG DATAFILE SIZE 20M AUTOEXTEND ON NEXT 10M MAXSIZE 1G;
CREATE TABLESPACE TS_SURABAYA DATAFILE SIZE 20M AUTOEXTEND ON NEXT 10M MAXSIZE 1G;
ALTER USER RS_APP QUOTA UNLIMITED ON TS_BANDUNG QUOTA UNLIMITED ON TS_SURABAYA;
SELECT CASE WHEN (SELECT COUNT(*) FROM dba_tablespaces WHERE tablespace_name IN ('TS_JAKARTA', 'TS_BANDUNG', 'TS_SURABAYA')) = 3 THEN 'LULUS: tiga tablespace alokasi tersedia di situs pusat' ELSE 'GAGAL: tiga tablespace alokasi tersedia di situs pusat' END AS cek FROM dual;
04_skema_global.sql LULUS · 2 cek jakarta · rs_app
- LULUS: enam tabel global terbentuk
- LULUS: enam kunci asing terpasang
Log SQL*Plus: 04_skema_global.log
-- ==========================================================================
-- Langkah 4 - Skema konseptual global (GCS)
-- Enam tabel Praktikum 2 dengan tipe data Oracle, PRIMARY KEY, FOREIGN KEY, dan CHECK.
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=jakarta sebagai=rs_app
-- Sambungan: RS_APP ke PDB situs JAKARTA.
-- Perbaikan terhadap skema praktikum asli:
-- * no_hp VARCHAR2, bukan NUMBER: nol di depan tidak boleh hilang.
-- * kolom kota sebagai kunci fragmentasi horizontal.
-- * ON DELETE CASCADE sesuai lembar praktikum (Oracle tidak punya ON UPDATE CASCADE).
CREATE TABLE PASIEN (
ID_PASIEN NUMBER(8) NOT NULL,
NAMA_PASIEN VARCHAR2(60),
ALAMAT_PASIEN VARCHAR2(150),
JENIS_KELAMIN CHAR(1),
PENYAKIT VARCHAR2(100),
NO_HP VARCHAR2(20),
KOTA VARCHAR2(40),
CONSTRAINT PK_PASIEN PRIMARY KEY (ID_PASIEN),
CONSTRAINT CK_PASIEN_JK CHECK (JENIS_KELAMIN IN ('L','P'))
);
CREATE TABLE DOKTER (
ID_DOKTER NUMBER(4) NOT NULL,
NAMA_DOKTER VARCHAR2(60),
ALAMAT_DOKTER VARCHAR2(150),
TANGGAL_LAHIR DATE,
NO_HP VARCHAR2(20),
SPESIALIS VARCHAR2(40),
WAKTU_KERJA VARCHAR2(60),
KOTA VARCHAR2(40),
CONSTRAINT PK_DOKTER PRIMARY KEY (ID_DOKTER)
);
CREATE TABLE ADMINISTRATOR (
ID_ADMIN NUMBER(4) NOT NULL,
NAMA_ADMIN VARCHAR2(60),
WAKTU_JAGA VARCHAR2(30),
KOTA VARCHAR2(40),
CONSTRAINT PK_ADMINISTRATOR PRIMARY KEY (ID_ADMIN)
);
CREATE TABLE PASIEN_DOKTER (
ID NUMBER(8) NOT NULL,
ID_DOKTER NUMBER(4) NOT NULL,
ID_PASIEN NUMBER(8) NOT NULL,
WAKTU_PERIKSA DATE,
RESEP VARCHAR2(150),
BIAYA NUMBER(12,2),
CONSTRAINT PK_PASIEN_DOKTER PRIMARY KEY (ID),
CONSTRAINT FK_PASIEN_DOKTER_DOKTER FOREIGN KEY (ID_DOKTER)
REFERENCES DOKTER (ID_DOKTER) ON DELETE CASCADE,
CONSTRAINT FK_PASIEN_DOKTER_PASIEN FOREIGN KEY (ID_PASIEN)
REFERENCES PASIEN (ID_PASIEN) ON DELETE CASCADE
);
CREATE TABLE DOKTER_ADMIN (
ID_DATA NUMBER(8) NOT NULL,
ID_DOKTER NUMBER(4) NOT NULL,
ID_ADMIN NUMBER(4) NOT NULL,
CONSTRAINT PK_DOKTER_ADMIN PRIMARY KEY (ID_DATA),
CONSTRAINT FK_DOKTER_ADMIN_DOKTER FOREIGN KEY (ID_DOKTER)
REFERENCES DOKTER (ID_DOKTER) ON DELETE CASCADE,
CONSTRAINT FK_DOKTER_ADMIN_ADMINISTRATOR FOREIGN KEY (ID_ADMIN)
REFERENCES ADMINISTRATOR (ID_ADMIN) ON DELETE CASCADE
);
CREATE TABLE DAFTAR (
ID_DAFTAR NUMBER(8) NOT NULL,
ID_PASIEN NUMBER(8) NOT NULL,
ID_ADMIN NUMBER(4) NOT NULL,
TANGGAL_DAFTAR DATE,
CONSTRAINT PK_DAFTAR PRIMARY KEY (ID_DAFTAR),
CONSTRAINT FK_DAFTAR_PASIEN FOREIGN KEY (ID_PASIEN)
REFERENCES PASIEN (ID_PASIEN) ON DELETE CASCADE,
CONSTRAINT FK_DAFTAR_ADMINISTRATOR FOREIGN KEY (ID_ADMIN)
REFERENCES ADMINISTRATOR (ID_ADMIN) ON DELETE CASCADE
);
-- Indeks penunjang join:
CREATE INDEX IX_PD_PASIEN ON PASIEN_DOKTER (ID_PASIEN);
CREATE INDEX IX_PD_DOKTER ON PASIEN_DOKTER (ID_DOKTER);
CREATE INDEX IX_DAFTAR_PASIEN ON DAFTAR (ID_PASIEN);
SELECT CASE WHEN (SELECT COUNT(*) FROM user_tables WHERE table_name IN ('PASIEN', 'DOKTER', 'ADMINISTRATOR', 'PASIEN_DOKTER', 'DOKTER_ADMIN', 'DAFTAR')) = 6 THEN 'LULUS: enam tabel global terbentuk' ELSE 'GAGAL: enam tabel global terbentuk' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM user_constraints WHERE constraint_type = 'R') = 6 THEN 'LULUS: enam kunci asing terpasang' ELSE 'GAGAL: enam kunci asing terpasang' END AS cek FROM dual;
05_data_contoh.sql LULUS · 7 cek jakarta · rs_app
- LULUS: pasien berisi 12 baris
- LULUS: dokter berisi 6 baris
- LULUS: administrator berisi 4 baris
- LULUS: pasien_dokter berisi 14 baris
- LULUS: dokter_admin berisi 8 baris
- LULUS: daftar berisi 12 baris
- LULUS: baris yang melanggar kendala tidak tersimpan
Galat peragaan yang memang harus muncul: ORA-02290 ORA-02291
Log SQL*Plus: 05_data_contoh.log
-- ==========================================================================
-- Langkah 5 - Data contoh
-- Dataset yang sama persis dengan Terminal SQL dan lab di situs.
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=jakarta sebagai=rs_app
-- @galat-diharapkan ORA-02290 ORA-02291
-- Sambungan: RS_APP ke PDB situs JAKARTA.
-- pasien (12 baris)
INSERT ALL
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (1, 'Budi Santoso', 'Jl. Merdeka 12', 'L', 'Demam Berdarah', '081234567001', 'Jakarta')
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (2, 'Siti Aminah', 'Jl. Kenanga 5', 'P', 'Tifus', '081234567002', 'Jakarta')
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (3, 'Andi Wijaya', 'Jl. Melati 9', 'L', 'Asma', '081234567003', 'Bandung')
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (4, 'Rina Marlina', 'Jl. Anggrek 21', 'P', 'Anemia', '081234567004', 'Bandung')
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (5, 'Joko Susilo', 'Jl. Mawar 3', 'L', 'Diabetes', '081234567005', 'Surabaya')
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (6, 'Dewi Lestari', 'Jl. Dahlia 7', 'P', 'Hipertensi', '081234567006', 'Surabaya')
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (7, 'Agus Setiawan', 'Jl. Cempaka 14', 'L', 'Demam Berdarah', '081234567007', 'Jakarta')
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (8, 'Nurul Hidayah', 'Jl. Flamboyan 2', 'P', 'Migrain', '081234567008', 'Bandung')
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (9, 'Hendra Gunawan', 'Jl. Teratai 18', 'L', 'Patah Tulang', '081234567009', 'Surabaya')
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (10, 'Maya Anggraini', 'Jl. Sakura 30', 'P', 'Tifus', '081234567010', 'Jakarta')
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (11, 'Rudi Hartono', 'Jl. Kamboja 11', 'L', 'Asma', '081234567011', 'Bandung')
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (12, 'Lina Wahyuni', 'Jl. Bougenville 6', 'P', 'Anemia', '081234567012', 'Surabaya')
SELECT * FROM dual;
-- dokter (6 baris)
INSERT ALL
INTO DOKTER (ID_DOKTER, NAMA_DOKTER, ALAMAT_DOKTER, TANGGAL_LAHIR, NO_HP, SPESIALIS, WAKTU_KERJA, KOTA) VALUES (1, 'dr. Surya Atmaja', 'Jl. Diponegoro 1', DATE '1975-04-12', '082100000001', 'Penyakit Dalam', 'Senin-Rabu', 'Jakarta')
INTO DOKTER (ID_DOKTER, NAMA_DOKTER, ALAMAT_DOKTER, TANGGAL_LAHIR, NO_HP, SPESIALIS, WAKTU_KERJA, KOTA) VALUES (2, 'dr. Anita Kusuma', 'Jl. Sudirman 44', DATE '1982-08-30', '082100000002', 'Anak', 'Selasa-Kamis', 'Jakarta')
INTO DOKTER (ID_DOKTER, NAMA_DOKTER, ALAMAT_DOKTER, TANGGAL_LAHIR, NO_HP, SPESIALIS, WAKTU_KERJA, KOTA) VALUES (3, 'dr. Bambang Riyadi', 'Jl. Asia Afrika 8', DATE '1969-01-05', '082100000003', 'Bedah', 'Rabu-Jumat', 'Bandung')
INTO DOKTER (ID_DOKTER, NAMA_DOKTER, ALAMAT_DOKTER, TANGGAL_LAHIR, NO_HP, SPESIALIS, WAKTU_KERJA, KOTA) VALUES (4, 'dr. Citra Dewanti', 'Jl. Braga 19', DATE '1988-11-21', '082100000004', 'Saraf', 'Senin-Kamis', 'Bandung')
INTO DOKTER (ID_DOKTER, NAMA_DOKTER, ALAMAT_DOKTER, TANGGAL_LAHIR, NO_HP, SPESIALIS, WAKTU_KERJA, KOTA) VALUES (5, 'dr. Eko Prabowo', 'Jl. Tunjungan 2', DATE '1979-06-17', '082100000005', 'Jantung', 'Selasa-Sabtu', 'Surabaya')
INTO DOKTER (ID_DOKTER, NAMA_DOKTER, ALAMAT_DOKTER, TANGGAL_LAHIR, NO_HP, SPESIALIS, WAKTU_KERJA, KOTA) VALUES (6, 'dr. Farah Nadia', 'Jl. Pemuda 27', DATE '1985-02-09', '082100000006', 'Paru', 'Senin-Jumat', 'Surabaya')
SELECT * FROM dual;
-- administrator (4 baris)
INSERT ALL
INTO ADMINISTRATOR (ID_ADMIN, NAMA_ADMIN, WAKTU_JAGA, KOTA) VALUES (1, 'Ratna Sari', 'Pagi', 'Jakarta')
INTO ADMINISTRATOR (ID_ADMIN, NAMA_ADMIN, WAKTU_JAGA, KOTA) VALUES (2, 'Dimas Prakoso', 'Siang', 'Jakarta')
INTO ADMINISTRATOR (ID_ADMIN, NAMA_ADMIN, WAKTU_JAGA, KOTA) VALUES (3, 'Yuni Astuti', 'Malam', 'Bandung')
INTO ADMINISTRATOR (ID_ADMIN, NAMA_ADMIN, WAKTU_JAGA, KOTA) VALUES (4, 'Tono Saputra', 'Pagi', 'Surabaya')
SELECT * FROM dual;
-- pasien_dokter (14 baris)
INSERT ALL
INTO PASIEN_DOKTER (ID, ID_DOKTER, ID_PASIEN, WAKTU_PERIKSA, RESEP, BIAYA) VALUES (1, 1, 1, DATE '2025-09-01', 'Paracetamol 500mg', 250000)
INTO PASIEN_DOKTER (ID, ID_DOKTER, ID_PASIEN, WAKTU_PERIKSA, RESEP, BIAYA) VALUES (2, 1, 2, DATE '2025-09-02', 'Amoxicillin 500mg', 275000)
INTO PASIEN_DOKTER (ID, ID_DOKTER, ID_PASIEN, WAKTU_PERIKSA, RESEP, BIAYA) VALUES (3, 2, 7, DATE '2025-09-02', 'Oralit + Vitamin', 180000)
INTO PASIEN_DOKTER (ID, ID_DOKTER, ID_PASIEN, WAKTU_PERIKSA, RESEP, BIAYA) VALUES (4, 2, 10, DATE '2025-09-03', 'Ciprofloxacin', 320000)
INTO PASIEN_DOKTER (ID, ID_DOKTER, ID_PASIEN, WAKTU_PERIKSA, RESEP, BIAYA) VALUES (5, 3, 3, DATE '2025-09-04', 'Salbutamol inhaler', 410000)
INTO PASIEN_DOKTER (ID, ID_DOKTER, ID_PASIEN, WAKTU_PERIKSA, RESEP, BIAYA) VALUES (6, 4, 8, DATE '2025-09-04', 'Sumatriptan', 390000)
INTO PASIEN_DOKTER (ID, ID_DOKTER, ID_PASIEN, WAKTU_PERIKSA, RESEP, BIAYA) VALUES (7, 3, 11, DATE '2025-09-05', 'Salbutamol inhaler', 410000)
INTO PASIEN_DOKTER (ID, ID_DOKTER, ID_PASIEN, WAKTU_PERIKSA, RESEP, BIAYA) VALUES (8, 4, 4, DATE '2025-09-05', 'Sulfas ferrosus', 210000)
INTO PASIEN_DOKTER (ID, ID_DOKTER, ID_PASIEN, WAKTU_PERIKSA, RESEP, BIAYA) VALUES (9, 5, 5, DATE '2025-09-08', 'Metformin 850mg', 450000)
INTO PASIEN_DOKTER (ID, ID_DOKTER, ID_PASIEN, WAKTU_PERIKSA, RESEP, BIAYA) VALUES (10, 5, 6, DATE '2025-09-08', 'Amlodipin 10mg', 380000)
INTO PASIEN_DOKTER (ID, ID_DOKTER, ID_PASIEN, WAKTU_PERIKSA, RESEP, BIAYA) VALUES (11, 6, 12, DATE '2025-09-09', 'Sulfas ferrosus', 215000)
INTO PASIEN_DOKTER (ID, ID_DOKTER, ID_PASIEN, WAKTU_PERIKSA, RESEP, BIAYA) VALUES (12, 3, 9, DATE '2025-09-10', 'Gips + Analgesik', 850000)
INTO PASIEN_DOKTER (ID, ID_DOKTER, ID_PASIEN, WAKTU_PERIKSA, RESEP, BIAYA) VALUES (13, 1, 1, DATE '2025-09-12', 'Kontrol - Paracetamol', 150000)
INTO PASIEN_DOKTER (ID, ID_DOKTER, ID_PASIEN, WAKTU_PERIKSA, RESEP, BIAYA) VALUES (14, 5, 5, DATE '2025-09-15', 'Kontrol - Metformin', 175000)
SELECT * FROM dual;
-- dokter_admin (8 baris)
INSERT ALL
INTO DOKTER_ADMIN (ID_DATA, ID_DOKTER, ID_ADMIN) VALUES (1, 1, 1)
INTO DOKTER_ADMIN (ID_DATA, ID_DOKTER, ID_ADMIN) VALUES (2, 2, 1)
INTO DOKTER_ADMIN (ID_DATA, ID_DOKTER, ID_ADMIN) VALUES (3, 1, 2)
INTO DOKTER_ADMIN (ID_DATA, ID_DOKTER, ID_ADMIN) VALUES (4, 2, 2)
INTO DOKTER_ADMIN (ID_DATA, ID_DOKTER, ID_ADMIN) VALUES (5, 3, 3)
INTO DOKTER_ADMIN (ID_DATA, ID_DOKTER, ID_ADMIN) VALUES (6, 4, 3)
INTO DOKTER_ADMIN (ID_DATA, ID_DOKTER, ID_ADMIN) VALUES (7, 5, 4)
INTO DOKTER_ADMIN (ID_DATA, ID_DOKTER, ID_ADMIN) VALUES (8, 6, 4)
SELECT * FROM dual;
-- daftar (12 baris)
INSERT ALL
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (1, 1, 1, DATE '2025-09-01')
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (2, 2, 1, DATE '2025-09-02')
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (3, 7, 2, DATE '2025-09-02')
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (4, 10, 2, DATE '2025-09-03')
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (5, 3, 3, DATE '2025-09-04')
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (6, 8, 3, DATE '2025-09-04')
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (7, 11, 3, DATE '2025-09-05')
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (8, 4, 3, DATE '2025-09-05')
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (9, 5, 4, DATE '2025-09-08')
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (10, 6, 4, DATE '2025-09-08')
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (11, 12, 4, DATE '2025-09-09')
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (12, 9, 3, DATE '2025-09-10')
SELECT * FROM dual;
COMMIT;
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN) = 12 THEN 'LULUS: pasien berisi 12 baris' ELSE 'GAGAL: pasien berisi 12 baris' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM DOKTER) = 6 THEN 'LULUS: dokter berisi 6 baris' ELSE 'GAGAL: dokter berisi 6 baris' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM ADMINISTRATOR) = 4 THEN 'LULUS: administrator berisi 4 baris' ELSE 'GAGAL: administrator berisi 4 baris' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN_DOKTER) = 14 THEN 'LULUS: pasien_dokter berisi 14 baris' ELSE 'GAGAL: pasien_dokter berisi 14 baris' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM DOKTER_ADMIN) = 8 THEN 'LULUS: dokter_admin berisi 8 baris' ELSE 'GAGAL: dokter_admin berisi 8 baris' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM DAFTAR) = 12 THEN 'LULUS: daftar berisi 12 baris' ELSE 'GAGAL: daftar berisi 12 baris' END AS cek FROM dual;
-- Kendala ditegakkan Oracle - kedua perintah di bawah SENGAJA ditolak:
INSERT INTO PASIEN (ID_PASIEN, NAMA_PASIEN, JENIS_KELAMIN, KOTA) VALUES (90, 'Uji CHECK', 'X', 'Jakarta');
INSERT INTO PASIEN_DOKTER (ID, ID_DOKTER, ID_PASIEN, BIAYA) VALUES (90, 42, 1, 1);
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN WHERE ID_PASIEN = 90) + (SELECT COUNT(*) FROM PASIEN_DOKTER WHERE ID = 90) = 0 THEN 'LULUS: baris yang melanggar kendala tidak tersimpan' ELSE 'GAGAL: baris yang melanggar kendala tidak tersimpan' END AS cek FROM dual;
06_fragmentasi_horizontal.sql LULUS · 6 cek jakarta · rs_app
- LULUS: fragmen P_JAKARTA berisi 4 baris dan berada di TS_JAKARTA
- LULUS: fragmen P_BANDUNG berisi 4 baris dan berada di TS_BANDUNG
- LULUS: fragmen P_SURABAYA berisi 4 baris dan berada di TS_SURABAYA
- LULUS: kelengkapan: jumlah semua fragmen = 12 baris
- LULUS: dengan ROW MOVEMENT baris berpindah ke fragmen Bandung
- LULUS: ROLLBACK mengembalikan baris ke fragmen Jakarta
Galat peragaan yang memang harus muncul: ORA-14402
Log SQL*Plus: 06_fragmentasi_horizontal.log
-- ==========================================================================
-- Langkah 6 - Fragmentasi horizontal primer
-- sigma_{kota = X}(PASIEN) diwujudkan sebagai PARTITION BY LIST, tanpa kehilangan data.
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=jakarta sebagai=rs_app
-- @galat-diharapkan ORA-14402
-- Sambungan: RS_APP ke PDB situs JAKARTA.
-- Aturan kebenaran:
-- Kelengkapan : partisi DEFAULT menampung kota yang belum terdaftar
-- Rekonstruksi : SELECT * FROM PASIEN menggabungkan seluruh partisi
-- Kedisjoinan : satu baris hanya bisa berada di satu partisi LIST
-- Tabel yang sudah berisi data diubah menjadi terpartisi secara ONLINE (Oracle 12.2+):
ALTER TABLE PASIEN MODIFY
PARTITION BY LIST (KOTA) (
PARTITION P_JAKARTA VALUES ('Jakarta') TABLESPACE TS_JAKARTA,
PARTITION P_BANDUNG VALUES ('Bandung') TABLESPACE TS_BANDUNG,
PARTITION P_SURABAYA VALUES ('Surabaya') TABLESPACE TS_SURABAYA,
PARTITION P_LAIN VALUES (DEFAULT)
) ONLINE UPDATE INDEXES;
-- Indeks LOCAL: satu segmen indeks per partisi. Operasi partisi (DROP/EXCHANGE)
-- tidak membuat indeks partisi lain invalid. Pilihan default untuk fragmentasi.
CREATE INDEX IX_PASIEN_NAMA ON PASIEN (NAMA_PASIEN) LOCAL;
-- Indeks GLOBAL: satu pohon indeks untuk seluruh tabel. Lebih cepat untuk
-- pencarian lintas partisi, tetapi operasi partisi membuatnya UNUSABLE
-- kecuali dipakai UPDATE INDEXES.
CREATE INDEX IX_PASIEN_HP ON PASIEN (NO_HP) GLOBAL PARTITION BY HASH (NO_HP) PARTITIONS 4;
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN PARTITION (P_JAKARTA)) = 4 AND (SELECT COUNT(*) FROM user_tab_partitions WHERE table_name = 'PASIEN' AND partition_name = 'P_JAKARTA' AND tablespace_name = 'TS_JAKARTA') = 1 THEN 'LULUS: fragmen P_JAKARTA berisi 4 baris dan berada di TS_JAKARTA' ELSE 'GAGAL: fragmen P_JAKARTA berisi 4 baris dan berada di TS_JAKARTA' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN PARTITION (P_BANDUNG)) = 4 AND (SELECT COUNT(*) FROM user_tab_partitions WHERE table_name = 'PASIEN' AND partition_name = 'P_BANDUNG' AND tablespace_name = 'TS_BANDUNG') = 1 THEN 'LULUS: fragmen P_BANDUNG berisi 4 baris dan berada di TS_BANDUNG' ELSE 'GAGAL: fragmen P_BANDUNG berisi 4 baris dan berada di TS_BANDUNG' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN PARTITION (P_SURABAYA)) = 4 AND (SELECT COUNT(*) FROM user_tab_partitions WHERE table_name = 'PASIEN' AND partition_name = 'P_SURABAYA' AND tablespace_name = 'TS_SURABAYA') = 1 THEN 'LULUS: fragmen P_SURABAYA berisi 4 baris dan berada di TS_SURABAYA' ELSE 'GAGAL: fragmen P_SURABAYA berisi 4 baris dan berada di TS_SURABAYA' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN) = 12 THEN 'LULUS: kelengkapan: jumlah semua fragmen = 12 baris' ELSE 'GAGAL: kelengkapan: jumlah semua fragmen = 12 baris' END AS cek FROM dual;
-- Memindah baris ke fragmen lain butuh ROW MOVEMENT. Perintah pertama SENGAJA ditolak:
UPDATE PASIEN SET KOTA = 'Bandung' WHERE ID_PASIEN = 1;
ALTER TABLE PASIEN ENABLE ROW MOVEMENT;
UPDATE PASIEN SET KOTA = 'Bandung' WHERE ID_PASIEN = 1;
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN PARTITION (P_BANDUNG) WHERE ID_PASIEN = 1) = 1 THEN 'LULUS: dengan ROW MOVEMENT baris berpindah ke fragmen Bandung' ELSE 'GAGAL: dengan ROW MOVEMENT baris berpindah ke fragmen Bandung' END AS cek FROM dual;
ROLLBACK;
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN PARTITION (P_JAKARTA) WHERE ID_PASIEN = 1) = 1 THEN 'LULUS: ROLLBACK mengembalikan baris ke fragmen Jakarta' ELSE 'GAGAL: ROLLBACK mengembalikan baris ke fragmen Jakarta' END AS cek FROM dual;
07_fragmentasi_turunan.sql LULUS · 5 cek jakarta · rs_app
- LULUS: partisi anak mewarisi 4 partisi induk
- LULUS: setiap pemeriksaan pasien Jakarta ikut berada di fragmen P_JAKARTA
- LULUS: setiap pemeriksaan pasien Bandung ikut berada di fragmen P_BANDUNG
- LULUS: setiap pemeriksaan pasien Surabaya ikut berada di fragmen P_SURABAYA
- LULUS: rencana memangkas ke satu partisi
Log SQL*Plus: 07_fragmentasi_turunan.log
-- ==========================================================================
-- Langkah 7 - Fragmentasi horizontal turunan (derived)
-- PASIEN_DOKTER_FRAG mengikuti fragmen PASIEN lewat PARTITION BY REFERENCE.
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=jakarta sebagai=rs_app
-- Sambungan: RS_APP ke PDB situs JAKARTA.
-- Syarat: kolom kunci asing NOT NULL dan tabel induk sudah terpartisi (Langkah 6).
-- Induk sudah ENABLE ROW MOVEMENT, maka anak juga wajib ROW MOVEMENT (tanpanya Oracle menolak: ORA-14661).
CREATE TABLE PASIEN_DOKTER_FRAG (
ID NUMBER(8) NOT NULL,
ID_DOKTER NUMBER(4) NOT NULL,
ID_PASIEN NUMBER(8) NOT NULL,
WAKTU_PERIKSA DATE,
RESEP VARCHAR2(150),
BIAYA NUMBER(12,2),
CONSTRAINT PK_PASIEN_DOKTER_FRAG PRIMARY KEY (ID),
CONSTRAINT FK_PDF_PASIEN FOREIGN KEY (ID_PASIEN) REFERENCES PASIEN (ID_PASIEN),
CONSTRAINT FK_PDF_DOKTER FOREIGN KEY (ID_DOKTER) REFERENCES DOKTER (ID_DOKTER)
)
PARTITION BY REFERENCE (FK_PDF_PASIEN)
ENABLE ROW MOVEMENT;
INSERT INTO PASIEN_DOKTER_FRAG SELECT * FROM PASIEN_DOKTER;
COMMIT;
SELECT CASE WHEN (SELECT COUNT(*) FROM user_tab_partitions WHERE table_name = 'PASIEN_DOKTER_FRAG') = 4 THEN 'LULUS: partisi anak mewarisi 4 partisi induk' ELSE 'GAGAL: partisi anak mewarisi 4 partisi induk' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN_DOKTER_FRAG PARTITION (P_JAKARTA)) = (SELECT COUNT(*) FROM PASIEN_DOKTER pd JOIN PASIEN p ON p.ID_PASIEN = pd.ID_PASIEN WHERE p.KOTA = 'Jakarta') THEN 'LULUS: setiap pemeriksaan pasien Jakarta ikut berada di fragmen P_JAKARTA' ELSE 'GAGAL: setiap pemeriksaan pasien Jakarta ikut berada di fragmen P_JAKARTA' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN_DOKTER_FRAG PARTITION (P_BANDUNG)) = (SELECT COUNT(*) FROM PASIEN_DOKTER pd JOIN PASIEN p ON p.ID_PASIEN = pd.ID_PASIEN WHERE p.KOTA = 'Bandung') THEN 'LULUS: setiap pemeriksaan pasien Bandung ikut berada di fragmen P_BANDUNG' ELSE 'GAGAL: setiap pemeriksaan pasien Bandung ikut berada di fragmen P_BANDUNG' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN_DOKTER_FRAG PARTITION (P_SURABAYA)) = (SELECT COUNT(*) FROM PASIEN_DOKTER pd JOIN PASIEN p ON p.ID_PASIEN = pd.ID_PASIEN WHERE p.KOTA = 'Surabaya') THEN 'LULUS: setiap pemeriksaan pasien Surabaya ikut berada di fragmen P_SURABAYA' ELSE 'GAGAL: setiap pemeriksaan pasien Surabaya ikut berada di fragmen P_SURABAYA' END AS cek FROM dual;
-- Rencana join: PARTITION LIST SINGLE pada kedua tabel menandakan join lokal satu fragmen.
EXPLAIN PLAN SET STATEMENT_ID = 'turunan' FOR
SELECT p.NAMA_PASIEN, pd.RESEP
FROM PASIEN p JOIN PASIEN_DOKTER_FRAG pd ON p.ID_PASIEN = pd.ID_PASIEN
WHERE p.KOTA = 'Jakarta';
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, 'turunan', 'BASIC +PARTITION'));
SELECT CASE WHEN (SELECT COUNT(*) FROM plan_table WHERE statement_id = 'turunan' AND operation LIKE 'PARTITION%' AND options = 'SINGLE') >= 1 THEN 'LULUS: rencana memangkas ke satu partisi' ELSE 'GAGAL: rencana memangkas ke satu partisi' END AS cek FROM dual;
08_fragmentasi_vertikal.sql LULUS · 2 cek jakarta · rs_app
- LULUS: rekonstruksi S1 JOIN S2 mengembalikan 10 pegawai
- LULUS: rekonstruksi memuat seluruh atribut asli
Log SQL*Plus: 08_fragmentasi_vertikal.log
-- ==========================================================================
-- Langkah 8 - Fragmentasi vertikal
-- STAFF dari Modul 6: data gaji (S1) dipisahkan dari data identitas (S2).
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=jakarta sebagai=rs_app
-- Sambungan: RS_APP ke PDB situs JAKARTA.
-- Syarat lossless-join: SETIAP fragmen memuat kunci STAFFNO.
CREATE TABLE S1_STAFF (
STAFFNO VARCHAR2(10) NOT NULL,
POSITION VARCHAR2(20),
SEX VARCHAR2(1),
DOB DATE,
SALARY NUMBER(5),
CONSTRAINT PK_S1_STAFF PRIMARY KEY (STAFFNO)
);
CREATE TABLE S2_STAFF (
STAFFNO VARCHAR2(10) NOT NULL,
FNAME VARCHAR2(10),
LNAME VARCHAR2(20),
BRANCHNO VARCHAR2(2),
CONSTRAINT PK_S2_STAFF PRIMARY KEY (STAFFNO),
CONSTRAINT FK_S2_STAFF_S1_STAFF FOREIGN KEY (STAFFNO)
REFERENCES S1_STAFF (STAFFNO) ON DELETE CASCADE
);
-- VIEW perekat: mengembalikan relasi global dari fragmen-fragmen vertikal.
-- Inilah "program lokalisasi" untuk fragmentasi vertikal: R = F1 JOIN F2 ... atas kunci.
CREATE OR REPLACE VIEW STAFF AS
SELECT S1_STAFF.STAFFNO,
S1_STAFF.POSITION,
S1_STAFF.SEX,
S1_STAFF.DOB,
S1_STAFF.SALARY,
S2_STAFF.FNAME,
S2_STAFF.LNAME,
S2_STAFF.BRANCHNO
FROM S1_STAFF
JOIN S2_STAFF ON S1_STAFF.STAFFNO = S2_STAFF.STAFFNO;
INSERT ALL
INTO S1_STAFF (STAFFNO, POSITION, SEX, DOB, SALARY) VALUES ('SL21', 'Manager', 'M', DATE '1945-10-01', 30000)
INTO S1_STAFF (STAFFNO, POSITION, SEX, DOB, SALARY) VALUES ('SG37', 'Assistant', 'F', DATE '1960-11-10', 12000)
INTO S1_STAFF (STAFFNO, POSITION, SEX, DOB, SALARY) VALUES ('SG14', 'Supervisor', 'M', DATE '1958-03-24', 18000)
INTO S1_STAFF (STAFFNO, POSITION, SEX, DOB, SALARY) VALUES ('SA9', 'Assistant', 'F', DATE '1970-02-19', 9000)
INTO S1_STAFF (STAFFNO, POSITION, SEX, DOB, SALARY) VALUES ('SG5', 'Manager', 'F', DATE '1940-06-03', 24000)
INTO S1_STAFF (STAFFNO, POSITION, SEX, DOB, SALARY) VALUES ('SL41', 'Assistant', 'F', DATE '1965-06-13', 9000)
INTO S1_STAFF (STAFFNO, POSITION, SEX, DOB, SALARY) VALUES ('SL22', 'Supervisor', 'M', DATE '1972-09-02', 17000)
INTO S1_STAFF (STAFFNO, POSITION, SEX, DOB, SALARY) VALUES ('SA11', 'Manager', 'F', DATE '1968-01-25', 27000)
INTO S1_STAFF (STAFFNO, POSITION, SEX, DOB, SALARY) VALUES ('SA14', 'Assistant', 'M', DATE '1980-12-05', 9500)
INTO S1_STAFF (STAFFNO, POSITION, SEX, DOB, SALARY) VALUES ('SG21', 'Assistant', 'F', DATE '1977-07-17', 11000)
SELECT * FROM dual;
INSERT ALL
INTO S2_STAFF (STAFFNO, FNAME, LNAME, BRANCHNO) VALUES ('SL21', 'John', 'White', 'B5')
INTO S2_STAFF (STAFFNO, FNAME, LNAME, BRANCHNO) VALUES ('SG37', 'Ann', 'Beech', 'B3')
INTO S2_STAFF (STAFFNO, FNAME, LNAME, BRANCHNO) VALUES ('SG14', 'David', 'Ford', 'B3')
INTO S2_STAFF (STAFFNO, FNAME, LNAME, BRANCHNO) VALUES ('SA9', 'Mary', 'Howe', 'B7')
INTO S2_STAFF (STAFFNO, FNAME, LNAME, BRANCHNO) VALUES ('SG5', 'Susan', 'Brand', 'B3')
INTO S2_STAFF (STAFFNO, FNAME, LNAME, BRANCHNO) VALUES ('SL41', 'Julie', 'Lee', 'B5')
INTO S2_STAFF (STAFFNO, FNAME, LNAME, BRANCHNO) VALUES ('SL22', 'Peter', 'Nugroho', 'B5')
INTO S2_STAFF (STAFFNO, FNAME, LNAME, BRANCHNO) VALUES ('SA11', 'Rina', 'Sitorus', 'B7')
INTO S2_STAFF (STAFFNO, FNAME, LNAME, BRANCHNO) VALUES ('SA14', 'Bimo', 'Prasetyo', 'B7')
INTO S2_STAFF (STAFFNO, FNAME, LNAME, BRANCHNO) VALUES ('SG21', 'Clara', 'Munthe', 'B3')
SELECT * FROM dual;
COMMIT;
SELECT CASE WHEN (SELECT COUNT(*) FROM STAFF) = 10 THEN 'LULUS: rekonstruksi S1 JOIN S2 mengembalikan 10 pegawai' ELSE 'GAGAL: rekonstruksi S1 JOIN S2 mengembalikan 10 pegawai' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM user_tab_columns WHERE table_name = 'STAFF') = 8 THEN 'LULUS: rekonstruksi memuat seluruh atribut asli' ELSE 'GAGAL: rekonstruksi memuat seluruh atribut asli' END AS cek FROM dual;
EXPLAIN PLAN SET STATEMENT_ID = 'vertikal' FOR SELECT FNAME, LNAME FROM S2_STAFF;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, 'vertikal', 'BASIC'));
09a_situs_bandung.sql LULUS · 3 cek bandung · rs_app
- LULUS: fragmen PASIEN Bandung berisi 4 baris
- LULUS: fragmen DAFTAR Bandung berisi 4 baris
- LULUS: baris yang salah situs tidak tersimpan
Galat peragaan yang memang harus muncul: ORA-02290
Log SQL*Plus: 09a_situs_bandung.log
-- ==========================================================================
-- Langkah 9a - Fragmen di situs Bandung
-- PASIEN_BANDUNG = sigma_{kota = 'Bandung'}(PASIEN); DAFTAR diturunkan dari fragmen itu.
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=bandung sebagai=rs_app
-- @galat-diharapkan ORA-02290
-- Sambungan: RS_APP ke PDB situs BANDUNG.
-- Kunci asing DAFTAR.ID_ADMIN tidak dideklarasikan: induknya ada di basis data lain,
-- dan Oracle tidak mengizinkan kunci asing lintas basis data.
CREATE TABLE PASIEN (
ID_PASIEN NUMBER(8) NOT NULL,
NAMA_PASIEN VARCHAR2(60),
ALAMAT_PASIEN VARCHAR2(150),
JENIS_KELAMIN CHAR(1),
PENYAKIT VARCHAR2(100),
NO_HP VARCHAR2(20),
KOTA VARCHAR2(40),
CONSTRAINT PK_PASIEN PRIMARY KEY (ID_PASIEN),
CONSTRAINT CK_PASIEN_JK CHECK (JENIS_KELAMIN IN ('L','P')),
CONSTRAINT CK_PASIEN_KOTA CHECK (KOTA = 'Bandung')
);
CREATE TABLE DAFTAR (
ID_DAFTAR NUMBER(8) NOT NULL,
ID_PASIEN NUMBER(8) NOT NULL,
ID_ADMIN NUMBER(4),
TANGGAL_DAFTAR DATE,
CONSTRAINT PK_DAFTAR PRIMARY KEY (ID_DAFTAR),
CONSTRAINT FK_DAFTAR_PASIEN FOREIGN KEY (ID_PASIEN)
REFERENCES PASIEN (ID_PASIEN) ON DELETE CASCADE
);
INSERT ALL
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (3, 'Andi Wijaya', 'Jl. Melati 9', 'L', 'Asma', '081234567003', 'Bandung')
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (4, 'Rina Marlina', 'Jl. Anggrek 21', 'P', 'Anemia', '081234567004', 'Bandung')
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (8, 'Nurul Hidayah', 'Jl. Flamboyan 2', 'P', 'Migrain', '081234567008', 'Bandung')
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (11, 'Rudi Hartono', 'Jl. Kamboja 11', 'L', 'Asma', '081234567011', 'Bandung')
SELECT * FROM dual;
INSERT ALL
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (5, 3, 3, DATE '2025-09-04')
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (6, 8, 3, DATE '2025-09-04')
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (7, 11, 3, DATE '2025-09-05')
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (8, 4, 3, DATE '2025-09-05')
SELECT * FROM dual;
COMMIT;
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN) = 4 THEN 'LULUS: fragmen PASIEN Bandung berisi 4 baris' ELSE 'GAGAL: fragmen PASIEN Bandung berisi 4 baris' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM DAFTAR) = 4 THEN 'LULUS: fragmen DAFTAR Bandung berisi 4 baris' ELSE 'GAGAL: fragmen DAFTAR Bandung berisi 4 baris' END AS cek FROM dual;
-- CHECK menjaga predikat fragmen: pasien kota lain SENGAJA ditolak.
INSERT INTO PASIEN (ID_PASIEN, NAMA_PASIEN, JENIS_KELAMIN, KOTA) VALUES (91, 'Salah situs', 'L', 'Jakarta');
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN WHERE ID_PASIEN = 91) = 0 THEN 'LULUS: baris yang salah situs tidak tersimpan' ELSE 'GAGAL: baris yang salah situs tidak tersimpan' END AS cek FROM dual;
09b_situs_surabaya.sql LULUS · 3 cek surabaya · rs_app
- LULUS: fragmen PASIEN Surabaya berisi 4 baris
- LULUS: fragmen DAFTAR Surabaya berisi 4 baris
- LULUS: baris yang salah situs tidak tersimpan
Galat peragaan yang memang harus muncul: ORA-02290
Log SQL*Plus: 09b_situs_surabaya.log
-- ==========================================================================
-- Langkah 9b - Fragmen di situs Surabaya
-- PASIEN_SURABAYA = sigma_{kota = 'Surabaya'}(PASIEN); DAFTAR diturunkan dari fragmen itu.
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=surabaya sebagai=rs_app
-- @galat-diharapkan ORA-02290
-- Sambungan: RS_APP ke PDB situs SURABAYA.
-- Kunci asing DAFTAR.ID_ADMIN tidak dideklarasikan: induknya ada di basis data lain,
-- dan Oracle tidak mengizinkan kunci asing lintas basis data.
CREATE TABLE PASIEN (
ID_PASIEN NUMBER(8) NOT NULL,
NAMA_PASIEN VARCHAR2(60),
ALAMAT_PASIEN VARCHAR2(150),
JENIS_KELAMIN CHAR(1),
PENYAKIT VARCHAR2(100),
NO_HP VARCHAR2(20),
KOTA VARCHAR2(40),
CONSTRAINT PK_PASIEN PRIMARY KEY (ID_PASIEN),
CONSTRAINT CK_PASIEN_JK CHECK (JENIS_KELAMIN IN ('L','P')),
CONSTRAINT CK_PASIEN_KOTA CHECK (KOTA = 'Surabaya')
);
CREATE TABLE DAFTAR (
ID_DAFTAR NUMBER(8) NOT NULL,
ID_PASIEN NUMBER(8) NOT NULL,
ID_ADMIN NUMBER(4),
TANGGAL_DAFTAR DATE,
CONSTRAINT PK_DAFTAR PRIMARY KEY (ID_DAFTAR),
CONSTRAINT FK_DAFTAR_PASIEN FOREIGN KEY (ID_PASIEN)
REFERENCES PASIEN (ID_PASIEN) ON DELETE CASCADE
);
INSERT ALL
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (5, 'Joko Susilo', 'Jl. Mawar 3', 'L', 'Diabetes', '081234567005', 'Surabaya')
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (6, 'Dewi Lestari', 'Jl. Dahlia 7', 'P', 'Hipertensi', '081234567006', 'Surabaya')
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (9, 'Hendra Gunawan', 'Jl. Teratai 18', 'L', 'Patah Tulang', '081234567009', 'Surabaya')
INTO PASIEN (ID_PASIEN, NAMA_PASIEN, ALAMAT_PASIEN, JENIS_KELAMIN, PENYAKIT, NO_HP, KOTA) VALUES (12, 'Lina Wahyuni', 'Jl. Bougenville 6', 'P', 'Anemia', '081234567012', 'Surabaya')
SELECT * FROM dual;
INSERT ALL
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (9, 5, 4, DATE '2025-09-08')
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (10, 6, 4, DATE '2025-09-08')
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (11, 12, 4, DATE '2025-09-09')
INTO DAFTAR (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (12, 9, 3, DATE '2025-09-10')
SELECT * FROM dual;
COMMIT;
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN) = 4 THEN 'LULUS: fragmen PASIEN Surabaya berisi 4 baris' ELSE 'GAGAL: fragmen PASIEN Surabaya berisi 4 baris' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM DAFTAR) = 4 THEN 'LULUS: fragmen DAFTAR Surabaya berisi 4 baris' ELSE 'GAGAL: fragmen DAFTAR Surabaya berisi 4 baris' END AS cek FROM dual;
-- CHECK menjaga predikat fragmen: pasien kota lain SENGAJA ditolak.
INSERT INTO PASIEN (ID_PASIEN, NAMA_PASIEN, JENIS_KELAMIN, KOTA) VALUES (91, 'Salah situs', 'L', 'Jakarta');
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN WHERE ID_PASIEN = 91) = 0 THEN 'LULUS: baris yang salah situs tidak tersimpan' ELSE 'GAGAL: baris yang salah situs tidak tersimpan' END AS cek FROM dual;
10_database_link.sql LULUS · 5 cek jakarta · rs_app
- LULUS: link SITUS_BANDUNG tersambung
- LULUS: link SITUS_SURABAYA tersambung
- LULUS: rekonstruksi dari tiga basis data = 12 pasien
- LULUS: isi view global identik dengan tabel global (MINUS dua arah kosong)
- LULUS: kedisjoinan: tidak ada ID pasien di dua situs
Log SQL*Plus: 10_database_link.log
-- ==========================================================================
-- Langkah 10 - Database link, sinonim, dan view global
-- Transparansi lokasi: aplikasi di Jakarta membaca ketiga situs seperti satu tabel.
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=jakarta sebagai=rs_app
-- Sambungan: RS_APP ke PDB situs JAKARTA.
-- &&tns_bandung dan &&tns_surabaya berisi alamat EZConnect situs, mis. //host:1521/BANDUNG.
CREATE DATABASE LINK SITUS_BANDUNG
CONNECT TO RS_APP IDENTIFIED BY "&&sandi_rs_app"
USING '&&tns_bandung';
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN@SITUS_BANDUNG) = 4 THEN 'LULUS: link SITUS_BANDUNG tersambung' ELSE 'GAGAL: link SITUS_BANDUNG tersambung' END AS cek FROM dual;
CREATE DATABASE LINK SITUS_SURABAYA
CONNECT TO RS_APP IDENTIFIED BY "&&sandi_rs_app"
USING '&&tns_surabaya';
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN@SITUS_SURABAYA) = 4 THEN 'LULUS: link SITUS_SURABAYA tersambung' ELSE 'GAGAL: link SITUS_SURABAYA tersambung' END AS cek FROM dual;
-- Fragmen disebut dengan nama tanpa lokasi (transparansi lokasi):
CREATE OR REPLACE VIEW PASIEN_JAKARTA AS SELECT * FROM PASIEN WHERE KOTA = 'Jakarta';
-- Transparansi lokasi tingkat DDBMS: aplikasi cukup menyebut nama objek,
-- letak fisiknya disembunyikan oleh sinonim. Pindah situs = ubah sinonim,
-- aplikasi tidak perlu disentuh sama sekali.
CREATE OR REPLACE SYNONYM PASIEN_BANDUNG FOR PASIEN@SITUS_BANDUNG;
CREATE OR REPLACE SYNONYM PASIEN_SURABAYA FOR PASIEN@SITUS_SURABAYA;
-- Program lokalisasi fragmentasi horizontal: R = F1 UNION ALL F2 UNION ALL ...
-- Oracle memangkas cabang yang predikatnya bertentangan dengan WHERE kueri
-- (partition pruning / predicate pushdown) — inilah reduksi yang dibahas di Modul 7.
CREATE OR REPLACE VIEW V_PASIEN_NASIONAL AS
SELECT * FROM PASIEN_JAKARTA
UNION ALL
SELECT * FROM PASIEN@SITUS_BANDUNG
UNION ALL
SELECT * FROM PASIEN@SITUS_SURABAYA
;
SELECT CASE WHEN (SELECT COUNT(*) FROM V_PASIEN_NASIONAL) = 12 THEN 'LULUS: rekonstruksi dari tiga basis data = 12 pasien' ELSE 'GAGAL: rekonstruksi dari tiga basis data = 12 pasien' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM (SELECT * FROM V_PASIEN_NASIONAL MINUS SELECT * FROM PASIEN)) + (SELECT COUNT(*) FROM (SELECT * FROM PASIEN MINUS SELECT * FROM V_PASIEN_NASIONAL)) = 0 THEN 'LULUS: isi view global identik dengan tabel global (MINUS dua arah kosong)' ELSE 'GAGAL: isi view global identik dengan tabel global (MINUS dua arah kosong)' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM (SELECT ID_PASIEN FROM V_PASIEN_NASIONAL GROUP BY ID_PASIEN HAVING COUNT(*) > 1)) = 0 THEN 'LULUS: kedisjoinan: tidak ada ID pasien di dua situs' ELSE 'GAGAL: kedisjoinan: tidak ada ID pasien di dua situs' END AS cek FROM dual;
SELECT db_link, username, host FROM user_db_links ORDER BY db_link;
11_replikasi_sumber.sql LULUS · 1 cek jakarta · rs_app
- LULUS: MV log DOKTER tersedia
Log SQL*Plus: 11_replikasi_sumber.log
-- ==========================================================================
-- Langkah 11 - Replikasi: MV log di situs sumber
-- DOKTER dibaca semua situs tetapi jarang berubah - kandidat replikasi.
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=jakarta sebagai=rs_app
-- Sambungan: RS_APP ke PDB situs JAKARTA.
-- MV log WAJIB dibuat di basis data pemilik tabel, bukan lewat database link.
-- MV log di situs sumber: mencatat perubahan agar refresh cepat (FAST) mungkin.
CREATE MATERIALIZED VIEW LOG ON DOKTER
WITH PRIMARY KEY, ROWID, SEQUENCE INCLUDING NEW VALUES;
SELECT CASE WHEN (SELECT COUNT(*) FROM user_mview_logs WHERE master = 'DOKTER') = 1 THEN 'LULUS: MV log DOKTER tersedia' ELSE 'GAGAL: MV log DOKTER tersedia' END AS cek FROM dual;
12_replikasi_replika.sql LULUS · 5 cek bandung · rs_app
- LULUS: replika berisi 6 dokter
- LULUS: sebelum refresh, replika masih nilai lama
- LULUS: setelah FAST refresh, replika mengikuti sumber
- LULUS: refresh terakhir berjenis FAST
- LULUS: refresh group RG_REFERENSI terdaftar
Log SQL*Plus: 12_replikasi_replika.log
-- ==========================================================================
-- Langkah 12 - Replikasi: materialized view di situs Bandung
-- Salinan DOKTER disegarkan FAST: hanya perubahan yang dikirim.
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=bandung sebagai=rs_app
-- Sambungan: RS_APP ke PDB situs BANDUNG.
CREATE DATABASE LINK SITUS_JAKARTA
CONNECT TO RS_APP IDENTIFIED BY "&&sandi_rs_app"
USING '&&tns_jakarta';
-- Prasyarat di situs sumber (SITUS_JAKARTA): CREATE MATERIALIZED VIEW LOG ON DOKTER WITH PRIMARY KEY, ROWID, SEQUENCE INCLUDING NEW VALUES;
-- Replika (salinan) di situs tujuan.
CREATE MATERIALIZED VIEW MV_DOKTER
BUILD IMMEDIATE
REFRESH FAST ON DEMAND
WITH PRIMARY KEY
AS SELECT * FROM DOKTER@SITUS_JAKARTA;
-- Segarkan manual:
EXEC DBMS_MVIEW.REFRESH('MV_DOKTER', 'F');
-- Pantau apakah replika tertinggal:
SELECT mview_name, last_refresh_type, last_refresh_date, staleness FROM user_mviews;
SELECT CASE WHEN (SELECT COUNT(*) FROM MV_DOKTER) = 6 THEN 'LULUS: replika berisi 6 dokter' ELSE 'GAGAL: replika berisi 6 dokter' END AS cek FROM dual;
-- Perubahan di sumber belum terlihat di replika sampai disegarkan (RPO asinkron):
UPDATE DOKTER@SITUS_JAKARTA SET WAKTU_KERJA = 'Senin-Sabtu' WHERE ID_DOKTER = 3;
COMMIT;
SELECT CASE WHEN (SELECT COUNT(*) FROM MV_DOKTER WHERE ID_DOKTER = 3 AND WAKTU_KERJA = 'Rabu-Jumat') = 1 THEN 'LULUS: sebelum refresh, replika masih nilai lama' ELSE 'GAGAL: sebelum refresh, replika masih nilai lama' END AS cek FROM dual;
EXEC DBMS_MVIEW.REFRESH('MV_DOKTER', 'F');
SELECT CASE WHEN (SELECT COUNT(*) FROM MV_DOKTER WHERE ID_DOKTER = 3 AND WAKTU_KERJA = 'Senin-Sabtu') = 1 THEN 'LULUS: setelah FAST refresh, replika mengikuti sumber' ELSE 'GAGAL: setelah FAST refresh, replika mengikuti sumber' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM user_mviews WHERE mview_name = 'MV_DOKTER' AND last_refresh_type = 'FAST') = 1 THEN 'LULUS: refresh terakhir berjenis FAST' ELSE 'GAGAL: refresh terakhir berjenis FAST' END AS cek FROM dual;
-- Refresh group menjaga konsistensi antar replika: seluruh MV di bawah ini
-- disegarkan dalam SATU transaksi, jadi tidak ada keadaan setengah jadi.
BEGIN
DBMS_REFRESH.MAKE(
name => 'RG_REFERENSI',
list => 'MV_DOKTER',
next_date => SYSDATE,
interval => 'SYSDATE + 15/1440',
implicit_destroy => FALSE);
END;
/
EXEC DBMS_REFRESH.REFRESH('RG_REFERENSI');
SELECT CASE WHEN (SELECT COUNT(*) FROM user_refresh WHERE rname = 'RG_REFERENSI') = 1 THEN 'LULUS: refresh group RG_REFERENSI terdaftar' ELSE 'GAGAL: refresh group RG_REFERENSI terdaftar' END AS cek FROM dual;
-- Kembalikan nilai sumber agar langkah berikutnya memakai data asli:
UPDATE DOKTER@SITUS_JAKARTA SET WAKTU_KERJA = 'Rabu-Jumat' WHERE ID_DOKTER = 3;
COMMIT;
13_transaksi_2pc.sql LULUS · 2 cek jakarta · rs_app
- LULUS: kedua situs menyimpan perubahan
- LULUS: ROLLBACK membatalkan perubahan lokal
Galat peragaan yang memang harus muncul: ORA-02290
Log SQL*Plus: 13_transaksi_2pc.log
-- ==========================================================================
-- Langkah 13 - Transaksi terdistribusi dan two-phase commit
-- Satu COMMIT atas dua basis data: Oracle menjalankan 2PC otomatis.
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=jakarta sebagai=rs_app
-- @galat-diharapkan ORA-02290
-- Sambungan: RS_APP ke PDB situs JAKARTA.
-- Transaksi terdistribusi. Oracle menjalankan two-phase commit SECARA OTOMATIS
-- begitu satu transaksi menyentuh lebih dari satu basis data lewat database link.
-- Tidak ada perintah khusus: cukup COMMIT.
SET TRANSACTION NAME 'RUJUK_PASIEN_LINTAS_KOTA';
-- situs Jakarta (lokal)
UPDATE PASIEN SET PENYAKIT = 'Asma - dirujuk' WHERE ID_PASIEN = 3;
-- situs Bandung (remote)
INSERT INTO DAFTAR@SITUS_BANDUNG (ID_DAFTAR, ID_PASIEN, ID_ADMIN, TANGGAL_DAFTAR) VALUES (99, 3, 3, DATE '2025-09-20');
-- Satu COMMIT ini memicu PREPARE ke 2 situs, lalu COMMIT global.
COMMIT;
-- Menunjuk situs commit point (paling tepercaya / paling jarang mati):
-- ALTER SESSION SET COMMIT_POINT_STRENGTH = 200; (parameter tingkat instans)
SELECT CASE WHEN (SELECT COUNT(*) FROM DAFTAR@SITUS_BANDUNG WHERE ID_DAFTAR = 99) = 1 AND (SELECT COUNT(*) FROM PASIEN WHERE ID_PASIEN = 3 AND PENYAKIT = 'Asma - dirujuk') = 1 THEN 'LULUS: kedua situs menyimpan perubahan' ELSE 'GAGAL: kedua situs menyimpan perubahan' END AS cek FROM dual;
-- Atomisitas global: perintah remote yang gagal membatalkan perubahan lokal juga.
UPDATE PASIEN SET PENYAKIT = 'Harus batal' WHERE ID_PASIEN = 1;
INSERT INTO PASIEN@SITUS_BANDUNG (ID_PASIEN, NAMA_PASIEN, JENIS_KELAMIN, KOTA) VALUES (92, 'Salah situs', 'L', 'Jakarta');
ROLLBACK;
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN WHERE ID_PASIEN = 1 AND PENYAKIT = 'Demam Berdarah') = 1 THEN 'LULUS: ROLLBACK membatalkan perubahan lokal' ELSE 'GAGAL: ROLLBACK membatalkan perubahan lokal' END AS cek FROM dual;
SELECT name, value FROM v$parameter WHERE name IN ('commit_point_strength', 'distributed_lock_timeout');
14a_matikan_pemulihan.sql LULUS · 0 cek cdb · sys
Log SQL*Plus: 14a_matikan_pemulihan.log
-- ==========================================================================
-- Langkah 14a - Matikan pemulihan otomatis (RECO) sementara
-- Agar transaksi ragu-ragu pada Langkah 14b tidak langsung diselesaikan proses RECO.
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=cdb sebagai=sys
-- Sambungan: SYS AS SYSDBA ke CDB$ROOT.
ALTER SYSTEM DISABLE DISTRIBUTED RECOVERY;
14b_transaksi_ragu_ragu.sql LULUS · 4 cek jakarta · rs_app
- LULUS: transaksi tercatat ragu-ragu (prepared) di DBA_2PC_PENDING
- LULUS: situs Bandung sudah COMMIT, jadi keputusan yang benar adalah COMMIT FORCE
- LULUS: status berubah menjadi forced commit
- LULUS: data pusat kini sama dengan Bandung
Galat peragaan yang memang harus muncul: ORA-02054 ORA-02059 ORA-01591
Log SQL*Plus: 14b_transaksi_ragu_ragu.log
-- ==========================================================================
-- Langkah 14b - Kegagalan 2PC, DBA_2PC_PENDING, dan COMMIT FORCE
-- Oracle mensimulasikan kegagalan lewat komentar ORA-2PC-CRASH-TEST-n.
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=jakarta sebagai=rs_app
-- @galat-diharapkan ORA-02054 ORA-02059 ORA-01591
-- Sambungan: RS_APP ke PDB situs JAKARTA.
-- Butuh hak FORCE ANY TRANSACTION di SEMUA situs yang terlibat (Langkah 2).
UPDATE PASIEN SET NO_HP = '081299990003' WHERE ID_PASIEN = 3;
UPDATE PASIEN@SITUS_BANDUNG SET NO_HP = '081299990003' WHERE ID_PASIEN = 3;
-- Titik kegagalan 7: situs Bandung sudah commit, situs pusat tertinggal di PREPARED.
COMMIT COMMENT 'ORA-2PC-CRASH-TEST-7';
-- Catatan in-doubt ditulis ke DBA_2PC_PENDING secara ASINKRON (beberapa detik); tunggu dulu:
DECLARE
n NUMBER;
BEGIN
FOR i IN 1 .. 60 LOOP
SELECT COUNT(*) INTO n FROM dba_2pc_pending WHERE state = 'prepared';
EXIT WHEN n > 0;
DBMS_SESSION.SLEEP(1);
END LOOP;
END;
/
COLUMN local_tran_id NEW_VALUE id_ragu
SELECT local_tran_id, global_tran_id, state, mixed FROM dba_2pc_pending WHERE state = 'prepared';
SELECT CASE WHEN (SELECT COUNT(*) FROM dba_2pc_pending WHERE state = 'prepared') = 1 THEN 'LULUS: transaksi tercatat ragu-ragu (prepared) di DBA_2PC_PENDING' ELSE 'GAGAL: transaksi tercatat ragu-ragu (prepared) di DBA_2PC_PENDING' END AS cek FROM dual;
-- Baris yang dikunci transaksi ragu-ragu tidak bisa dibaca - perintah ini SENGAJA gagal (ORA-01591):
SELECT NO_HP FROM PASIEN WHERE ID_PASIEN = 3;
-- Sebelum memaksa keputusan, DBA memeriksa keputusan di situs lain:
SELECT state FROM dba_2pc_pending@SITUS_BANDUNG;
SELECT CASE WHEN (SELECT COUNT(*) FROM dba_2pc_pending@SITUS_BANDUNG WHERE state = 'committed') = 1 THEN 'LULUS: situs Bandung sudah COMMIT, jadi keputusan yang benar adalah COMMIT FORCE' ELSE 'GAGAL: situs Bandung sudah COMMIT, jadi keputusan yang benar adalah COMMIT FORCE' END AS cek FROM dual;
-- Kueri lewat database link di atas membuka transaksi; tanpa COMMIT ini Oracle menolak COMMIT FORCE (ORA-02043).
COMMIT;
COMMIT FORCE '&id_ragu';
SELECT CASE WHEN (SELECT COUNT(*) FROM dba_2pc_pending WHERE state = 'forced commit') = 1 THEN 'LULUS: status berubah menjadi forced commit' ELSE 'GAGAL: status berubah menjadi forced commit' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM PASIEN WHERE ID_PASIEN = 3 AND NO_HP = '081299990003') = 1 AND (SELECT COUNT(*) FROM PASIEN@SITUS_BANDUNG WHERE ID_PASIEN = 3 AND NO_HP = '081299990003') = 1 THEN 'LULUS: data pusat kini sama dengan Bandung' ELSE 'GAGAL: data pusat kini sama dengan Bandung' END AS cek FROM dual;
COMMIT;
-- Catatan "forced commit" tetap tersimpan sampai DBA membersihkannya (Langkah 14d).
14c_nyalakan_pemulihan.sql LULUS · 0 cek cdb · sys
Log SQL*Plus: 14c_nyalakan_pemulihan.log
-- ==========================================================================
-- Langkah 14c - Nyalakan kembali pemulihan otomatis (RECO)
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=cdb sebagai=sys
-- Sambungan: SYS AS SYSDBA ke CDB$ROOT.
ALTER SYSTEM ENABLE DISTRIBUTED RECOVERY;
14d_bersihkan_catatan_2pc.sql LULUS · 1 cek jakarta · sys
- LULUS: tidak ada lagi catatan transaksi prepared atau forced di situs pusat
Log SQL*Plus: 14d_bersihkan_catatan_2pc.log
-- ==========================================================================
-- Langkah 14d - DBA membersihkan catatan transaksi yang sudah diputuskan paksa
-- DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY butuh hak SYS; entri forced commit/rollback tidak hilang sendiri.
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=jakarta sebagai=sys
-- Sambungan: SYS AS SYSDBA ke PDB situs JAKARTA.
SELECT local_tran_id, state FROM dba_2pc_pending;
BEGIN
FOR t IN (SELECT local_tran_id FROM dba_2pc_pending WHERE state IN ('forced commit', 'forced rollback')) LOOP
DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY(t.local_tran_id);
COMMIT;
END LOOP;
END;
/
SELECT CASE WHEN (SELECT COUNT(*) FROM dba_2pc_pending WHERE state IN ('prepared', 'forced commit', 'forced rollback')) = 0 THEN 'LULUS: tidak ada lagi catatan transaksi prepared atau forced di situs pusat' ELSE 'GAGAL: tidak ada lagi catatan transaksi prepared atau forced di situs pusat' END AS cek FROM dual;
15_transparansi_lima_tingkat.sql LULUS · 4 cek jakarta · rs_app
- LULUS: tingkat 1 (Transparansi fragmentasi) menghasilkan 6 pasien perempuan
- LULUS: tingkat 2 (Transparansi lokasi) menghasilkan 6 pasien perempuan
- LULUS: tingkat 3 (Transparansi pemetaan lokal) menghasilkan 6 pasien perempuan
- LULUS: replika dokter terbaca lewat sinonim
Log SQL*Plus: 15_transparansi_lima_tingkat.log
-- ==========================================================================
-- Langkah 15 - Satu kueri pada tingkat-tingkat transparansi
-- Hasil setiap tingkat wajib sama; yang berbeda hanya seberapa banyak lokasi yang harus ditulis.
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=jakarta sebagai=rs_app
-- Sambungan: RS_APP ke PDB situs JAKARTA.
-- Notasi modul "SELECT ... FROM PASIEN_JAKARTA AT SITE S1" BUKAN sintaks Oracle.
-- Padanannya di Oracle adalah tingkat 3 di bawah: nama_tabel@database_link.
-- TINGKAT 1: Transparansi fragmentasi
-- Kueri ke relasi global; fragmen maupun situs tidak disebut.
SELECT NAMA_PASIEN, PENYAKIT FROM V_PASIEN_NASIONAL WHERE JENIS_KELAMIN = 'P';
SELECT CASE WHEN (SELECT COUNT(*) FROM (SELECT NAMA_PASIEN, PENYAKIT FROM V_PASIEN_NASIONAL WHERE JENIS_KELAMIN = 'P')) = 6 THEN 'LULUS: tingkat 1 (Transparansi fragmentasi) menghasilkan 6 pasien perempuan' ELSE 'GAGAL: tingkat 1 (Transparansi fragmentasi) menghasilkan 6 pasien perempuan' END AS cek FROM dual;
-- TINGKAT 2: Transparansi lokasi
-- Fragmen disebut namanya, situsnya disembunyikan view dan sinonim.
SELECT NAMA_PASIEN, PENYAKIT FROM PASIEN_JAKARTA WHERE JENIS_KELAMIN = 'P'
UNION ALL
SELECT NAMA_PASIEN, PENYAKIT FROM PASIEN_BANDUNG WHERE JENIS_KELAMIN = 'P'
UNION ALL
SELECT NAMA_PASIEN, PENYAKIT FROM PASIEN_SURABAYA WHERE JENIS_KELAMIN = 'P';
SELECT CASE WHEN (SELECT COUNT(*) FROM (SELECT NAMA_PASIEN, PENYAKIT FROM PASIEN_JAKARTA WHERE JENIS_KELAMIN = 'P'
UNION ALL
SELECT NAMA_PASIEN, PENYAKIT FROM PASIEN_BANDUNG WHERE JENIS_KELAMIN = 'P'
UNION ALL
SELECT NAMA_PASIEN, PENYAKIT FROM PASIEN_SURABAYA WHERE JENIS_KELAMIN = 'P')) = 6 THEN 'LULUS: tingkat 2 (Transparansi lokasi) menghasilkan 6 pasien perempuan' ELSE 'GAGAL: tingkat 2 (Transparansi lokasi) menghasilkan 6 pasien perempuan' END AS cek FROM dual;
-- TINGKAT 3: Transparansi pemetaan lokal
-- Fragmen DAN lokasinya disebut: partisi lokal dan tabel@database_link.
SELECT NAMA_PASIEN, PENYAKIT FROM PASIEN PARTITION (P_JAKARTA) WHERE JENIS_KELAMIN = 'P'
UNION ALL
SELECT NAMA_PASIEN, PENYAKIT FROM PASIEN@SITUS_BANDUNG WHERE JENIS_KELAMIN = 'P'
UNION ALL
SELECT NAMA_PASIEN, PENYAKIT FROM PASIEN@SITUS_SURABAYA WHERE JENIS_KELAMIN = 'P';
SELECT CASE WHEN (SELECT COUNT(*) FROM (SELECT NAMA_PASIEN, PENYAKIT FROM PASIEN PARTITION (P_JAKARTA) WHERE JENIS_KELAMIN = 'P'
UNION ALL
SELECT NAMA_PASIEN, PENYAKIT FROM PASIEN@SITUS_BANDUNG WHERE JENIS_KELAMIN = 'P'
UNION ALL
SELECT NAMA_PASIEN, PENYAKIT FROM PASIEN@SITUS_SURABAYA WHERE JENIS_KELAMIN = 'P')) = 6 THEN 'LULUS: tingkat 3 (Transparansi pemetaan lokal) menghasilkan 6 pasien perempuan' ELSE 'GAGAL: tingkat 3 (Transparansi pemetaan lokal) menghasilkan 6 pasien perempuan' END AS cek FROM dual;
-- TINGKAT 4: Transparansi replikasi
-- Aplikasi membaca salinan terdekat lewat sinonim; ia tidak tahu salinan mana yang dipakai.
CREATE OR REPLACE SYNONYM DOKTER_TERDEKAT FOR MV_DOKTER@SITUS_BANDUNG;
SELECT NAMA_DOKTER, SPESIALIS FROM DOKTER_TERDEKAT;
SELECT CASE WHEN (SELECT COUNT(*) FROM DOKTER_TERDEKAT) = 6 THEN 'LULUS: replika dokter terbaca lewat sinonim' ELSE 'GAGAL: replika dokter terbaca lewat sinonim' END AS cek FROM dual;
-- TINGKAT 5: Tanpa transparansi - aplikasi sendiri yang merutekan ke setiap situs
-- (tiga kueri terpisah, digabung oleh aplikasi):
SELECT NAMA_PASIEN, PENYAKIT FROM PASIEN PARTITION (P_JAKARTA) WHERE JENIS_KELAMIN = 'P';
SELECT NAMA_PASIEN, PENYAKIT FROM PASIEN@SITUS_BANDUNG WHERE JENIS_KELAMIN = 'P';
SELECT NAMA_PASIEN, PENYAKIT FROM PASIEN@SITUS_SURABAYA WHERE JENIS_KELAMIN = 'P';
16_rencana_eksekusi.sql LULUS · 3 cek jakarta · rs_app
- LULUS: predikat kota memangkas akses ke SATU partisi
- LULUS: tanpa predikat, SELURUH partisi dibaca
- LULUS: join lintas situs memuat operasi REMOTE
Log SQL*Plus: 16_rencana_eksekusi.log
-- ==========================================================================
-- Langkah 16 - Membaca rencana eksekusi
-- Bukti partition pruning (reduksi lokalisasi) dan operasi REMOTE.
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=jakarta sebagai=rs_app
-- Sambungan: RS_APP ke PDB situs JAKARTA.
EXEC DBMS_STATS.GATHER_SCHEMA_STATS('RS_APP', cascade => TRUE);
EXPLAIN PLAN SET STATEMENT_ID = 'pruning' FOR SELECT * FROM PASIEN WHERE KOTA = 'Jakarta';
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, 'pruning', 'BASIC +PARTITION'));
SELECT CASE WHEN (SELECT COUNT(*) FROM plan_table WHERE statement_id = 'pruning' AND operation = 'PARTITION LIST' AND options = 'SINGLE') = 1 THEN 'LULUS: predikat kota memangkas akses ke SATU partisi' ELSE 'GAGAL: predikat kota memangkas akses ke SATU partisi' END AS cek FROM dual;
EXPLAIN PLAN SET STATEMENT_ID = 'semua' FOR SELECT * FROM PASIEN;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, 'semua', 'BASIC +PARTITION'));
SELECT CASE WHEN (SELECT COUNT(*) FROM plan_table WHERE statement_id = 'semua' AND operation = 'PARTITION LIST' AND options = 'ALL') = 1 THEN 'LULUS: tanpa predikat, SELURUH partisi dibaca' ELSE 'GAGAL: tanpa predikat, SELURUH partisi dibaca' END AS cek FROM dual;
EXPLAIN PLAN SET STATEMENT_ID = 'remote' FOR
SELECT p.NAMA_PASIEN, d.ID_DAFTAR FROM PASIEN p JOIN DAFTAR@SITUS_BANDUNG d ON p.ID_PASIEN = d.ID_PASIEN;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, 'remote', 'BASIC +REMOTE'));
SELECT CASE WHEN (SELECT COUNT(*) FROM plan_table WHERE statement_id = 'remote' AND operation = 'REMOTE') >= 1 THEN 'LULUS: join lintas situs memuat operasi REMOTE' ELSE 'GAGAL: join lintas situs memuat operasi REMOTE' END AS cek FROM dual;
SELECT other FROM plan_table WHERE statement_id = 'remote' AND operation = 'REMOTE';
-- Kolom OTHER di atas berisi SQL yang benar-benar dikirim ke situs Bandung.
17_diagnosa.sql LULUS · 2 cek jakarta · rs_app
- LULUS: tidak ada transaksi yang masih menggantung
- LULUS: seluruh objek RS_APP valid
Log SQL*Plus: 17_diagnosa.log
-- ==========================================================================
-- Langkah 17 - Diagnosa DBA: transaksi menggantung, kunci, dan deadlock
-- Dihasilkan oleh ORACLEDECK (node tools/gen_oracle.js) - jangan sunting manual.
-- ==========================================================================
-- @jalankan situs=jakarta sebagai=rs_app
-- Sambungan: RS_APP ke PDB situs JAKARTA.
-- Transaksi terdistribusi yang menggantung (in-doubt):
SELECT local_tran_id, global_tran_id, state, mixed, advice, host, commit#
FROM dba_2pc_pending
ORDER BY fail_time;
-- Sisi mana yang belum menjawab:
SELECT local_tran_id, in_out, database, dbuser_owner, interface, dbid
FROM dba_2pc_neighbors;
-- Kunci yang tertahan gara-gara transaksi in-doubt:
SELECT s.sid, s.serial#, s.username, l.type, l.id1, l.id2, l.lmode, l.request
FROM v$lock l JOIN v$session s ON s.sid = l.sid
WHERE l.type IN ('TX','TM') AND l.request > 0;
-- Deadlock: Oracle mendeteksi sendiri dan melempar ORA-00060 ke salah satu sesi.
-- Rinciannya ada di trace file yang ditunjuk:
SELECT value FROM v$diag_info WHERE name = 'Default Trace File';
-- Paksa keputusan HANYA bila keputusan koordinator sudah dipastikan:
-- COMMIT FORCE '<local_tran_id>';
-- ROLLBACK FORCE '<local_tran_id>';
-- EXEC DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('<local_tran_id>');
-- Siapa menunggu siapa (wait-for graph versi Oracle):
SELECT s.sid, s.username, s.blocking_session, s.event, s.seconds_in_wait
FROM v$session s
WHERE s.blocking_session IS NOT NULL;
SELECT CASE WHEN (SELECT COUNT(*) FROM dba_2pc_pending WHERE state = 'prepared') = 0 THEN 'LULUS: tidak ada transaksi yang masih menggantung' ELSE 'GAGAL: tidak ada transaksi yang masih menggantung' END AS cek FROM dual;
SELECT CASE WHEN (SELECT COUNT(*) FROM user_objects WHERE status <> 'VALID') = 0 THEN 'LULUS: seluruh objek RS_APP valid' ELSE 'GAGAL: seluruh objek RS_APP valid' END AS cek FROM dual;
Menjalankan sendiri
Otomatis (butuh Docker; image gvenzl/oracle-free:23-slim ±2,8 GB):
node tools/uji_oracle.mjs # siapkan 3 situs dari nol, jalankan semua skrip
node tools/verifikasi_oracle.mjs # bandingkan mesin terminal dengan Oracle
Manual dengan SQL*Plus: baris -- @jalankan situs=... sebagai=... di awal setiap skrip menyebut basis data dan pengguna yang dipakai; sandi diminta lewat variabel &&sandi_rs_app. Urutan lengkap ada di 00_URUTAN_JALANKAN.md.