Dari tabel ke graf

Foreign key menjadi relasi: resep impor dari basis data relasional.

Daftar isi

Topik ini membahas pemetaan skema basis data relasional ke dalam graf Neo4j dengan fokus pada aturan intinya, yaitu foreign key (kunci penunjuk antartabel) menjadi relasi. Contoh yang digunakan adalah empat tabel kecil, yaitu pelanggan, itemtoko, pesanan, dan tabel jembatan pesanan_item yang menghubungkan pesanan dengan barang. Resep impornya terdiri atas dua tahap, yakni impor node per tabel lebih dahulu, kemudian impor relasi dengan MATCH pada kedua ujungnya. Seluruh tahap dijalankan dengan LOAD CSV pada server Neo4j 2026.10, sehingga setiap kueri dapat direproduksi apa adanya. Setelah topik ini dibaca, hasil yang diperoleh adalah graf berisi label :Pelanggan, :Pesanan, dan :ItemToko yang terhubung melalui relasi :MEMESAN serta :MEMUAT.

Pemetaan dari relasional ke graf#

Kesepadanan antara tabel relasional dan struktur graf mengikuti aturan pemetaan yang tetap. Setiap baris pada tabel entitas menjadi satu node, nama tabel menjadi label node, dan setiap kolom menjadi properti node dengan nilai barisnya. Foreign key, yaitu kolom yang menyimpan kunci baris pada tabel lain, diganti menjadi relasi dengan tipe yang diberi nama. Kolom foreign key itu sendiri tidak dipertahankan sebagai properti karena relasinya sudah menyimpan keterhubungan tersebut. Tabel jembatan (junction table) yang memuat hubungan many-to-many menjadi relasi, sedangkan kolom tambahannya, misalnya jumlah, menjadi properti pada relasi itu.

Skema contoh menggunakan tabel pelanggan dengan kolom id dan nama, tabel itemtoko dengan kolom yang sama, serta tabel pesanan yang memuat kolom nomor dan kolom foreign key pelangganId. Keterhubungan pesanan dengan barang dicatat pada tabel jembatan pesanan_item yang berisi kolom nomorPesanan, itemId, dan jumlah. Tabel berikut merangkum pemetaan lengkapnya.

Elemen relasionalPadanan pada grafContoh pada dataset
Tabel pelangganlabel :Pelanggantiga baris menjadi tiga node
Tabel itemtokolabel :ItemTokotiga baris menjadi tiga node
Tabel pesananlabel :Pesananempat baris menjadi empat node
Kolom namaproperti namanilai barisnya menjadi nilai properti
Foreign key pelangganIdrelasi :MEMESANPesanan → Pelanggan
Tabel jembatan pesanan_itemrelasi :MEMUAT berpropertikolom jumlah menjadi properti relasi

Dataset contoh dan penempatan berkas CSV#

Empat berkas CSV berikut mewakili empat tabel tersebut, dengan baris header dan datanya dalam bentuk yang sama persis dengan hasil ekspor dari basis data relasional. Tidak ada pengolahan yang dilakukan pada berkasnya, karena seluruh konversi tipe dan pembentukan relasi dikerjakan oleh kueri Cypher pada tahap impor. Keempatnya tersimpan di samping index.md sebagai aset topik ini.

Perhatian khusus diberikan pada berkas pesanan_item.csv. Tabel jembatan tidak memiliki kunci miliknya sendiri, karena makna setiap barisnya terletak pada pasangan kolom nomorPesanan dan itemId, sedangkan kolom jumlah menjadi keterangan pada pasangan tersebut. Struktur semacam ini justru yang dikehendaki pada graf, yaitu pasangan kunci yang kelak menjadi relasi dan kolom tambahan yang kelak menjadi properti relasinya.

csv pelanggan.csv
id,nama
1,Sanggar Cempaka
2,Koperasi Pena
3,Studio Angin
csv itemtoko.csv
id,nama
1,Buku Ajar Graf
2,Kalender Meja
3,Kertas Gambar A3
csv pesanan.csv
nomor,pelangganId
1001,1
1002,1
1003,2
1004,3
csv pesanan_item.csv
nomorPesanan,itemId,jumlah
1001,1,2
1001,3,1
1002,2,3
1003,1,1
1003,3,2
1004,2,1

Sebelum kueri LOAD CSV dijalankan, keempat berkas disalin ke folder import milik NEO4J_HOME karena jalur file:/// hanya menjangkau folder itu. Penyalinan cukup dilakukan satu kali, dan salinan tersebut berdiri sendiri tanpa mengubah berkas aslinya.

bash
# tidak-dijalankan
# salin keempat berkas CSV ke folder import milik NEO4J_HOME
cp pelanggan.csv itemtoko.csv pesanan.csv pesanan_item.csv "$NEO4J_HOME"/import/

Tahap satu: impor node per tabel#

Tahap pertama membawa setiap baris tabel menjadi node, satu kueri per tabel, dan belum ada relasi yang dibuat pada tahap ini. Klausa LOAD CSV WITH HEADERS membaca berkas per baris dengan nama kolom dari header, sedangkan MERGE mengunci setiap node pada kunci bisnisnya agar kueri aman dijalankan ulang tanpa menimbulkan duplikat. Fungsi toInteger wajib ada karena LOAD CSV selalu menghasilkan string, sedangkan kunci id dan nomor berupa bilangan bulat pada skema aslinya. Nama variabel baris pada kueri bersifat bebas dan hanya menjadi pengikat nilai satu baris CSV. Kueri berikut mengimpor tabel pelanggan dan menampilkan hasilnya.

cypher
LOAD CSV WITH HEADERS FROM 'file:///pelanggan.csv' AS baris
MERGE (p:Pelanggan {id: toInteger(baris.id)})
  SET p.nama = baris.nama;
MATCH (p:Pelanggan)
RETURN p.id AS id, p.nama AS nama
ORDER BY id;
text Keluaran
id, nama
1, "Sanggar Cempaka"
2, "Koperasi Pena"
3, "Studio Angin"

Kueri yang sama berlaku untuk tabel itemtoko, dengan perbedaan hanya pada label dan nama berkasnya.

cypher
LOAD CSV WITH HEADERS FROM 'file:///itemtoko.csv' AS baris
MERGE (i:ItemToko {id: toInteger(baris.id)})
  SET i.nama = baris.nama;
MATCH (i:ItemToko)
RETURN i.id AS id, i.nama AS nama
ORDER BY id;
text Keluaran
id, nama
1, "Buku Ajar Graf"
2, "Kalender Meja"
3, "Kertas Gambar A3"

Tabel pesanan diimpor dengan pola yang serupa, tetapi kolom foreign key pelangganId ikut disimpan sebagai properti perantara. Properti tersebut belum menjadi relasi, sehingga keluarannya masih menampilkan angka referensi, bukan nama pelanggan.

cypher
LOAD CSV WITH HEADERS FROM 'file:///pesanan.csv' AS baris
MERGE (ps:Pesanan {nomor: toInteger(baris.nomor)})
  SET ps.pelangganId = toInteger(baris.pelangganId);
MATCH (ps:Pesanan)
RETURN ps.nomor AS nomor, ps.pelangganId AS pelangganId
ORDER BY nomor;
text Keluaran
nomor, pelangganId
1001, 1
1002, 1
1003, 2
1004, 3

Tahap dua: impor relasi dengan MATCH#

Tahap kedua mengubah kolom foreign key menjadi relasi. Resepnya selalu sama, yaitu mengikat kedua node ujung dengan MATCH, kemudian menuliskan pola relasinya pada MERGE. Pola relasi tidak pernah ditulis dengan node ujung yang belum terikat karena MERGE akan membuat node anonim baru untuk setiap baris. Kueri berikut menghubungkan setiap node :Pesanan dengan node :Pelanggan yang direferensikan oleh pelangganId.

cypher
MATCH (ps:Pesanan), (p:Pelanggan {id: ps.pelangganId})
MERGE (ps)-[:MEMESAN]->(p);
MATCH (ps:Pesanan)-[:MEMESAN]->(p:Pelanggan)
RETURN ps.nomor AS nomor, p.nama AS pelanggan
ORDER BY nomor;
text Keluaran
nomor, pelanggan
1001, "Sanggar Cempaka"
1002, "Sanggar Cempaka"
1003, "Koperasi Pena"
1004, "Studio Angin"

Keluaran menampilkan nama pelanggan, bukan angka referensi, karena keterhubungan kini tersimpan sebagai relasi. Tabel jembatan pesanan_item diperlakukan dengan resep yang sama, dengan tambahan satu langkah, yaitu kolom jumlah yang menjadi properti pada relasi :MEMUAT.

cypher
LOAD CSV WITH HEADERS FROM 'file:///pesanan_item.csv' AS baris
MATCH (ps:Pesanan {nomor: toInteger(baris.nomorPesanan)})
MATCH (i:ItemToko {id: toInteger(baris.itemId)})
MERGE (ps)-[m:MEMUAT]->(i)
  SET m.jumlah = toInteger(baris.jumlah);
MATCH (ps:Pesanan)-[m:MEMUAT]->(i:ItemToko)
RETURN ps.nomor AS nomor, i.nama AS item, m.jumlah AS jumlah
ORDER BY nomor, item;
text Keluaran
nomor, item, jumlah
1001, "Buku Ajar Graf", 2
1001, "Kertas Gambar A3", 1
1002, "Kalender Meja", 3
1003, "Buku Ajar Graf", 1
1003, "Kertas Gambar A3", 2
1004, "Kalender Meja", 1

Setelah kedua tahap selesai, properti perantara pelangganId sudah kehilangan fungsinya dan dilepas dengan REMOVE agar tidak ada dua sumber kebenaran untuk satu keterhubungan.

cypher
MATCH (ps:Pesanan)
REMOVE ps.pelangganId;
MATCH (ps:Pesanan)
RETURN ps.nomor AS nomor, ps.pelangganId AS pelangganId
ORDER BY nomor;
text Keluaran
nomor, pelangganId
1001, NULL
1002, NULL
1003, NULL
1004, NULL

Memeriksa hasil pemetaan#

Graf hasil impor diperiksa dari dua sisi, yaitu jumlah objeknya dan jalur yang tersimpan. Jumlah node pada dataset ini adalah tiga node :Pelanggan, empat node :Pesanan, dan tiga node :ItemToko, sedangkan jumlah relasinya empat relasi :MEMESAN dan enam relasi :MEMUAT. Angka relasi :MEMUAT sama dengan banyaknya baris pada tabel jembatan, karena setiap baris jembatan menjadi satu relasi. Pemeriksaan jumlah layak dilakukan karena mesin tidak melaporkan baris yang dilewati, sehingga kekurangan relasi tidak pernah muncul sebagai error. Kueri berikut menelusuri jalur lengkap dari pelanggan hingga barang yang dimuat pesanannya, sehingga seluruh pemetaan terlihat dalam satu keluaran.

cypher
MATCH (p:Pelanggan)<-[:MEMESAN]-(ps:Pesanan)-[m:MEMUAT]->(i:ItemToko)
RETURN p.nama AS pelanggan, ps.nomor AS nomor, i.nama AS item, m.jumlah AS jumlah
ORDER BY nomor, item;
text Keluaran
pelanggan, nomor, item, jumlah
"Sanggar Cempaka", 1001, "Buku Ajar Graf", 2
"Sanggar Cempaka", 1001, "Kertas Gambar A3", 1
"Sanggar Cempaka", 1002, "Kalender Meja", 3
"Koperasi Pena", 1003, "Buku Ajar Graf", 1
"Koperasi Pena", 1003, "Kertas Gambar A3", 2
"Studio Angin", 1004, "Kalender Meja", 1

Setiap baris keluaran memuat nama pelanggan, nomor pesanan, nama barang, dan jumlahnya dalam satu jalur tersimpan. Ketika salah satu tahap impor terlewat, baris yang bersangkutan hilang dari keluaran ini, sehingga penelusuran jalur sekaligus menjadi pemeriksaan kelengkapan yang mudah. Pertanyaan keterhubungan semacam ini dijawab graf dengan penelusuran relasi, tanpa JOIN yang dihitung saat kueri berjalan, karena relasi antarentitas sudah berbentuk data.

Kesalahan umum#

Tiga kesalahan berikut paling sering muncul saat resep ini dipraktikkan.

  1. Impor relasi dijalankan sebelum node ujungnya ada. MATCH tidak menemukan pasangan, kueri tetap berjalan sukses tanpa membuat apa pun, dan kekurangan relasi baru terlihat jauh setelahnya. Perbaikannya adalah memastikan seluruh kueri tahap satu sudah dijalankan dan jumlah node setiap label sudah sesuai sebelum tahap dua dimulai.
  2. Nilai kunci tidak dikonversi dengan toInteger. Pola {id: baris.id} mencocokkan string "1" dengan bilangan 1 yang tersimpan, dan kedua nilai itu tidak pernah sama, sehingga relasi tidak terbentuk untuk baris mana pun. Perbaikannya adalah membungkus setiap kolom kunci dengan toInteger, sebagaimana seluruh contoh pada topik ini.
  3. Properti foreign key dipertahankan setelah relasinya terbentuk. Graf menyimpan keterhubungan yang sama pada dua tempat, dan keduanya dapat berbeda isi ketika data diperbarui. Perbaikannya adalah melepas properti perantara dengan REMOVE setelah relasi terverifikasi.

Ringkasan#

  • Setiap baris tabel menjadi node dengan label dari nama tabelnya, sedangkan kolomnya menjadi properti node.
  • Foreign key menjadi tipe relasi, dan tabel jembatan many-to-many menjadi relasi berproperti.
  • Resep impornya dua tahap, yaitu impor node per tabel dengan MERGE berkunci, kemudian impor relasi dengan mengikat kedua ujungnya melalui MATCH.
  • Kolom foreign key dilepas dengan REMOVE setelah relasi penggantinya terbentuk agar keterhubungan tidak tersimpan dua kali.

Referensi#