Lewati ke isi
ORACLEDECK

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.

21/21skrip lulus
60pemeriksaan LULUS
0GAGAL
60/60kueri & DML identik
39/39kode galat sama

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

Topologi uji: situs Jakarta terhubung ke Bandung dan Surabaya lewat database link JAKARTA (pusat) FREEPDB1 skema global · partisi · view global BANDUNG fragmen PASIEN & DAFTAR replika MV_DOKTER SURABAYA fragmen PASIEN & DAFTAR SITUS_BANDUNG SITUS_SURABAYA

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

TemuanGejala di OraclePerbaikan
Reference partitioning butuh ROW MOVEMENTORA-14661 saat tabel anak dibuat, karena induknya sudah ENABLE ROW MOVEMENTTabel anak ikut ENABLE ROW MOVEMENT
Catatan in-doubt ditulis asinkronTepat setelah ORA-02054, DBA_2PC_PENDING masih kosong; baris baru muncul beberapa detik kemudianSkrip menunggu dengan polling sebelum membaca ID transaksi
Kueri lewat link membuka transaksiORA-02043: COMMIT FORCE ditolak karena SELECT ke dba_2pc_pending@SITUS_BANDUNG belum diakhiriCOMMIT sebelum COMMIT FORCE
Membersihkan catatan 2PC butuh hak SYSDBMS_TRANSACTION.PURGE_LOST_DB_ENTRY gagal sebagai pemilik skemaDipindah ke langkah 14d sebagai SYS
Oracle memeriksa kolom saat parseKolom salah pada tabel kosong tetap ORA-00904/ORA-00918/ORA-00979; mesin terminal lama meloloskannyaValidasi semantik statis di mesin SQL
SELECT atas baris terkunci in-doubtORA-01591 — pembaca pun ditolak, bukan melihat data lamaTerminal menolak baca/tulis tabel yang dikunci transaksi ragu-ragu
TRUNCATE, CHECK kolom, dan format tanggalTRUNCATE induk lolos bila anak kosong; CHECK kolom yang menyebut kolom lain ORA-02438; 31/12/2025ORA-01830Mesin 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.

ArtiOracleTerminal
nilai PRIMARY KEY sudah dipakai
INSERT INTO g01 VALUES (1)
ORA-00001ORA-00001
nilai UNIQUE sudah dipakai
INSERT INTO g02 VALUES (2, 'A1')
ORA-00001ORA-00001
kolom NOT NULL tidak diisi
INSERT INTO g03 (id) VALUES (1)
ORA-01400ORA-01400
teks melebihi panjang VARCHAR2
INSERT INTO g04 VALUES ('abcd')
ORA-12899ORA-12899
angka melebihi presisi NUMBER(p,s)
INSERT INTO g05 VALUES (1234)
ORA-01438ORA-01438
teks dimasukkan ke kolom NUMBER
INSERT INTO g06 VALUES ('abc')
ORA-01722ORA-01722
kendala CHECK dilanggar
INSERT INTO g07 VALUES ('X')
ORA-02290ORA-02290
nilai FOREIGN KEY tidak ada di tabel induk
INSERT INTO g08b VALUES (1, 9)
ORA-02291ORA-02291
baris induk masih dirujuk (tanpa CASCADE)
DELETE FROM g09a
ORA-02292ORA-02292
tabel atau view tidak ada
SELECT * FROM g10_tidak_ada
ORA-00942ORA-00942
kolom tidak dikenal
SELECT b FROM g11
ORA-00904ORA-00904
nama kolom ada di dua tabel yang di-join
SELECT id FROM g12a JOIN g12b ON g12a.id = g12b.id
ORA-00918ORA-00918
nama objek sudah dipakai
CREATE TABLE g13 (b NUMBER(4))
ORA-00955ORA-00955
nama kolom ganda
CREATE TABLE g14 (a NUMBER(4), a NUMBER(4))
ORA-00957ORA-00957
tabel induk masih dirujuk kunci asing
DROP TABLE g15a
ORA-02449ORA-02449
TRUNCATE tabel induk yang anaknya masih berisi baris
TRUNCATE TABLE g16a
ORA-02266ORA-02266
kolom SELECT tidak ikut GROUP BY
SELECT a, b FROM g17 GROUP BY a
ORA-00979ORA-00979
kolom biasa bercampur fungsi agregat tanpa GROUP BY
SELECT a, COUNT(*) FROM g18
ORA-00937ORA-00937
fungsi agregat di WHERE (seharusnya HAVING)
SELECT a FROM g29 WHERE COUNT(*) > 1
ORA-00934ORA-00934
daftar nama kolom view tidak sama dengan kolom kueri
CREATE VIEW g19v (x, y) AS SELECT a FROM g19
ORA-01730ORA-01730
UNIQUE INDEX pada data yang sudah ganda
CREATE UNIQUE INDEX g20i ON g20 (a)
ORA-01452ORA-01452
DML pada view gabungan UNION ALL
DELETE FROM g21v
ORA-01732ORA-01732
kunci asing merujuk kolom yang bukan PRIMARY KEY/UNIQUE
CREATE TABLE g22b (x VARCHAR2(10) REFERENCES g22a (nama))
ORA-02270ORA-02270
tabel induk tidak punya PRIMARY KEY
CREATE TABLE g23b (x NUMBER(4) REFERENCES g23a)
ORA-02268ORA-02268
jumlah kolom kunci asing tidak sama dengan kunci induk
CREATE TABLE g24b (x NUMBER(4), FOREIGN KEY (x) REFERENCES g24a (a, b))
ORA-02256ORA-02256
VARCHAR2 tanpa panjang
CREATE TABLE g25 (a VARCHAR2)
ORA-00906ORA-00906
CHECK tingkat kolom menyebut kolom lain
CREATE TABLE g26 (a NUMBER(4), b NUMBER(4) CHECK (a > 1))
ORA-02438ORA-02438
CHECK tingkat tabel menyebut kolom yang tidak ada
CREATE TABLE g26b (a NUMBER(4), CHECK (b > 1))
ORA-00904ORA-00904
ROLLBACK TO savepoint yang belum dibuat
ROLLBACK TO SAVEPOINT g27_tidak_ada
ORA-01086ORA-01086
DROP INDEX yang tidak ada
DROP INDEX g28_tidak_ada
ORA-01418ORA-01418
COMMIT FORCE untuk ID transaksi yang tidak ragu-ragu
COMMIT FORCE '9.99.999'
ORA-02058ORA-02058
fungsi tidak dikenal
SELECT fungsi_tidak_ada(1) FROM dual
ORA-00904ORA-00904
tanggal gaya DD/MM/YYYY pada format YYYY-MM-DD: masih ada sisa input
INSERT INTO g30 VALUES ('31/12/2025')
ORA-01830ORA-01830
bulan 13
INSERT INTO g32 VALUES ('2025-13-01')
ORA-01843ORA-01843
hari 32
INSERT INTO g33 VALUES ('2025-01-32')
ORA-01847ORA-01847
29 Februari pada tahun bukan kabisat
INSERT INTO g34 VALUES ('2025-02-29')
ORA-01839ORA-01839
tanggal tanpa hari
INSERT INTO g35 VALUES ('2025-09')
ORA-01840ORA-01840
teks yang bukan tanggal
INSERT INTO g36 VALUES ('kemarin')
ORA-01841ORA-01841
DESC objek yang tidak ada
DESC g31_tidak_ada
ORA-04043ORA-04043

Hasil lengkap termasuk 53 kueri, 7 skrip DML, dan 4 skrip \ekspor: VERIFIKASI_MESIN.md.

Pemetaan konsep ke fitur Oracle

Konsep kuliahFitur OracleSkrip
Tiga situsPluggable database + database link01, 02, 10
Fragmentasi horizontal primerALTER TABLE ... MODIFY PARTITION BY LIST ... ONLINE06
Fragmentasi horizontal turunanPARTITION BY REFERENCE07
Fragmentasi vertikalTabel terpisah + VIEW perekat atas kunci08
Alokasi fragmenTablespace per situs; fragmen di PDB situsnya03, 09
Transparansi lokasiSYNONYM + VIEW UNION ALL10, 15
Replikasi asinkronMV log di sumber + REFRESH FAST ON DEMAND di replika11, 12
Two-phase commitCOMMIT atas dua basis data13
Transaksi ragu-raguORA-2PC-CRASH-TEST-7, DBA_2PC_PENDING, COMMIT FORCE14a–14d
Reduksi lokalisasiPartition pruning: PARTITION LIST SINGLE07, 16
Join terdistribusiOperasi REMOTE pada rencana eksekusi16

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.