LOAD CSV

Header, pemisah, dan subquery ter-batch pada LOAD CSV.

Daftar isi

LOAD CSV membaca baris demi baris dari berkas CSV dan mengalirkannya ke dalam kueri Cypher, sehingga berkas datar dapat diubah menjadi node dan relasi. Topik ini membahas URL file:/// yang diselesaikan terhadap direktori import server, pembacaan baris header (baris nama kolom) dengan WITH HEADERS, pemisah kolom alternatif melalui FIELDTERMINATOR, pola baris-ke-node dengan MERGE, serta subquery ter-batch CALL { … } IN TRANSACTIONS untuk muatan besar. Seluruh contoh dijalankan pada server Neo4j 2026.10 dengan dataset pegawai dan divisi modul ini. Setelah topik ini dibaca, berkas CSV kecil dapat diimpor menjadi graf secara berulang tanpa duplikat.

Berkas contoh#

Dua berkas CSV digunakan di seluruh contoh pada topik ini. Berkas pegawai.csv memuat satu baris header dan lima baris data pegawai, sedangkan divisi.csv memuat kode singkat dan nama lengkap untuk tiga divisi. Kolom divisi pada pegawai.csv menyimpan nama divisi yang lengkap, sehingga nilainya dapat dicocokkan langsung dengan kolom nama pada divisi.csv. Baris header menyediakan nama kunci untuk setiap kolom, dan kunci itulah yang digunakan kueri untuk mengambil nilai pada setiap baris.

Kedua berkas berikut dibuat pada direktori kerja. Seluruh nilainya berupa teks biasa, termasuk angka tahun pada kolom tahunMasuk, karena format CSV tidak menyimpan tipe data. Nama pegawai berfungsi sebagai kunci alami, yaitu nilai yang membedakan satu pegawai dari pegawai lain, sehingga kunci inilah yang nantinya dituliskan pada pola MERGE. Kelima pegawai terdistribusi ke ketiga divisi, sehingga setiap divisi memuat paling sedikit satu relasi anggota.

csv pegawai.csv
nama,divisi,tahunMasuk
Agus,Infra,2019
Bella,Data,2021
Candra,Produk,2018
Dinda,Data,2022
Eri,Infra,2020
csv divisi.csv
kode,nama
DT,Data
IF,Infra
PR,Produk

Isi kedua berkas pada direktori kerja dapat diperiksa dengan perintah head sebelum salinannya dikirim ke server.

bash
# menampilkan isi kedua berkas contoh dari terminal
head pegawai.csv divisi.csv
text Keluaran
==> pegawai.csv <==
nama,divisi,tahunMasuk
Agus,Infra,2019
Bella,Data,2021
Candra,Produk,2018
Dinda,Data,2022
Eri,Infra,2020

==> divisi.csv <==
kode,nama
DT,Data
IF,Infra
PR,Produk

Direktori import dan URL file:///#

URL file:/// pada LOAD CSV diselesaikan terhadap direktori import server Neo4j, bukan terhadap direktori kerja terminal. Pada instalasi baku, direktori tersebut adalah folder import di bawah $NEO4J_HOME, sedangkan pengaturan server.directories.import dapat mengubah lokasinya. Mekanisme ini membatasi akses berkas oleh kueri pada satu direktori, sehingga berkas lain pada server tidak terbaca oleh LOAD CSV. Nama berkas setelah file:/// bersifat relatif, sehingga file:///pegawai.csv menunjuk pegawai.csv yang berada langsung di dalam direktori tersebut.

Perintah berikut menyalin seluruh berkas CSV contoh ke direktori import.

bash
# tidak-dijalankan
# menyalin berkas CSV ke direktori import milik instalasi Neo4j
cp pegawai.csv divisi.csv divisi-titik-koma.csv $NEO4J_HOME/import/

Sebelum pola penulisannya disusun, isi berkas diperiksa terlebih dahulu dengan kueri baca yang hanya mengembalikan nilai kolomnya.

cypher
LOAD CSV WITH HEADERS FROM 'file:///pegawai.csv' AS baris
RETURN baris.nama AS nama, baris.divisi AS divisi, baris.tahunMasuk AS tahunMasuk
ORDER BY baris.nama;
text Keluaran
nama, divisi, tahunMasuk
"Agus", "Infra", "2019"
"Bella", "Data", "2021"
"Candra", "Produk", "2018"
"Dinda", "Data", "2022"
"Eri", "Infra", "2020"

Keluaran menunjukkan dua hal yang menentukan bentuk kueri berikutnya. Setiap baris terbaca lengkap sesuai header karena WITH HEADERS menjadikan setiap baris sebuah map, yaitu struktur data berkunci nama kolom. Seluruh nilai termasuk tahunMasuk datang sebagai string yang ditandai kutip ganda, karena LOAD CSV menganggap seluruh isi CSV sebagai teks. Konversi tipe dengan toInteger() karenanya dilakukan saat penulisan data ke node.

Pola baris-ke-node dengan MERGE#

Impor graf dari berkas CSV menempuh pola baris-ke-node, yaitu setiap baris data diubah menjadi satu node melalui MERGE yang mencari dahulu sebelum membuat. Impor dijalankan bertahap, dimulai dari node divisi sebagai target relasi pegawai. Kueri berikut mengimpor divisi.csv beserta verifikasinya.

cypher
LOAD CSV WITH HEADERS FROM 'file:///divisi.csv' AS baris
MERGE (d:Divisi {kode: baris.kode})
SET d.nama = baris.nama;
MATCH (d:Divisi)
RETURN d.kode AS kode, d.nama AS nama
ORDER BY d.kode;
text Keluaran
kode, nama
"DT", "Data"
"IF", "Infra"
"PR", "Produk"

Properti kode pada pola MERGE berperan sebagai kunci pencocokan, sedangkan nama lengkap divisi diberikan melalui SET agar tidak ikut menentukan pencocokan. Pola ini idempoten, artinya keadaan akhir sama pada setiap eksekusi, karena setiap baris menemukan node hasil eksekusi sebelumnya. Berkas pegawai.csv diimpor dengan pola yang sama, tetapi kolom tahunMasuk melewati toInteger().

cypher
LOAD CSV WITH HEADERS FROM 'file:///pegawai.csv' AS baris
MERGE (k:Karyawan {nama: baris.nama})
SET k.divisi = baris.divisi,
    k.tahunMasuk = toInteger(baris.tahunMasuk);
MATCH (k:Karyawan)
RETURN k.nama AS nama, k.divisi AS divisi, k.tahunMasuk AS tahunMasuk
ORDER BY k.nama;
text Keluaran
nama, divisi, tahunMasuk
"Agus", "Infra", 2019
"Bella", "Data", 2021
"Candra", "Produk", 2018
"Dinda", "Data", 2022
"Eri", "Infra", 2020

Selisihnya terlihat pada keluaran, yaitu tahunMasuk yang kini tampil tanpa kutip karena telah tersimpan sebagai bilangan bulat.

Relasi antara karyawan dan divisi dibentuk pada tahap ketiga dengan membaca ulang berkas yang sama. Setiap baris kini mengikat dua node yang sudah ada melalui MATCH, yaitu karyawan berdasarkan kolom nama dan divisi berdasarkan kolom divisi, kemudian pola relasi :ANGGOTA_DARI dituliskan pada MERGE. Urutannya saling bergantung, karena MATCH pada tahap terakhir menuntut kedua ujung relasinya sudah berada di dalam graf.

cypher
LOAD CSV WITH HEADERS FROM 'file:///pegawai.csv' AS baris
MATCH (k:Karyawan {nama: baris.nama})
MATCH (d:Divisi {nama: baris.divisi})
MERGE (k)-[:ANGGOTA_DARI]->(d);
MATCH (k:Karyawan)-[:ANGGOTA_DARI]->(d:Divisi)
RETURN k.nama AS nama, d.nama AS divisi
ORDER BY k.nama;
text Keluaran
nama, divisi
"Agus", "Infra"
"Bella", "Data"
"Candra", "Produk"
"Dinda", "Data"
"Eri", "Infra"

Keluaran memuat lima relasi, satu untuk setiap pegawai, semuanya menghubungkan node yang sudah ada.

Pemisah kolom dengan FIELDTERMINATOR#

Opsi FIELDTERMINATOR mengganti pemisah kolom baku, yaitu koma, dengan karakter lain semacam titik koma yang lazim pada berkas ekspor dari sistem lain. Berkas divisi-titik-koma.csv memuat data yang sama dengan divisi.csv, tetapi setiap kolomnya dipisahkan oleh titik koma.

csv divisi-titik-koma.csv
kode;nama
DT;Data
IF;Infra
PR;Produk

Apabila berkas semacam ini dibaca tanpa opsi pemisah, LOAD CSV menganggapnya berisi satu kolom saja, dan kunci kode tidak pernah ditemukan. Kueri berikut memperlihatkan keadaannya.

cypher
LOAD CSV WITH HEADERS FROM 'file:///divisi-titik-koma.csv' AS baris
RETURN baris.kode AS kode
ORDER BY baris.kode;
text Keluaran
kode
NULL
NULL
NULL

Keluaran memuat tiga nilai NULL karena seluruh baris jatuh ke satu kolom yang bernama kode;nama, sehingga rujukan baris.kode menemui kunci yang tidak ada. Opsi FIELDTERMINATOR ';' dituliskan setelah variabel baris untuk memperbaiki pembacaan berkas yang sama.

cypher
LOAD CSV WITH HEADERS FROM 'file:///divisi-titik-koma.csv' AS baris FIELDTERMINATOR ';'
RETURN baris.kode AS kode, baris.nama AS nama
ORDER BY baris.kode;
text Keluaran
kode, nama
"DT", "Data"
"IF", "Infra"
"PR", "Produk"

Kali ini kolom terpisah dengan benar dan ketiga divisi terbaca utuh, sama seperti divisi.csv. Karakter pemisah lain dapat dituliskan melalui kode Unicode empat digit, misalnya \u003B untuk titik koma. Opsi ini hanya mengubah cara kolom dibaca, berlaku dengan maupun tanpa header, sedangkan aturan kutip pada berkas tetap sama.

Muatan besar dengan subquery ter-batch#

LOAD CSV menjalankan seluruh kuerinya dalam satu transaksi, sehingga berkas dengan ratusan ribu baris berpotensi menghabiskan memori server. Penanganannya berupa subquery ter-batch CALL { … } IN TRANSACTIONS yang membagi eksekusi menjadi transaksi-transaksi kecil dengan commit (penyimpanan permanen transaksi) di antaranya. Klausa USING PERIODIC COMMIT yang dahulu menangani pembagian ini sudah tidak tersedia sejak Neo4j 5, sehingga subquery ter-batch menjadi gantinya.

Variabel baris dimasukkan ke dalam subquery melalui klausa lingkup variabel CALL (baris), sedangkan bentuk lama CALL { WITH baris … } masih diterima tetapi sudah dinyatakan deprecated, yaitu masih berjalan tanpa jaminan ketersediaan. Ukuran kelompok ditetapkan dengan IN TRANSACTIONS OF n ROWS, dan tanpanya nilai bakuannya adalah 1000 baris per transaksi. Kueri berikut mengimpor ulang pegawai.csv dengan kelompok 500 baris.

cypher
LOAD CSV WITH HEADERS FROM 'file:///pegawai.csv' AS baris
CALL (baris) {
  MERGE (k:Karyawan {nama: baris.nama})
  SET k.divisi = baris.divisi,
      k.tahunMasuk = toInteger(baris.tahunMasuk)
} IN TRANSACTIONS OF 500 ROWS;
MATCH (k:Karyawan)
RETURN count(k) AS jumlah;
text Keluaran
jumlah
5

Pada berkas lima baris ini hanya satu transaksi dalam yang terbentuk, sehingga hasilnya sama persis dengan impor tanpa subquery. Manfaatnya baru terlihat pada berkas besar, ketika ratusan ribu baris terbagi menjadi ratusan transaksi ringan. Subquery ini hanya berjalan pada implicit transaction, yaitu transaksi yang dibuka otomatis untuk satu pernyataan, sehingga Neo4j Browser menuntut awalan :auto, sedangkan cypher-shell non-interaktif menjalankannya apa adanya.

Kesalahan umum#

Empat kesalahan berikut paling sering muncul saat topik ini dipraktikkan. Setiap butir memuat gejala yang teramati beserta penyebab dan perbaikannya.

  1. Berkas CSV tidak berada di dalam direktori import. Kueri ditolak dengan pesan error unable to load external resource, tanpa data yang ditulis. Perbaikannya adalah menyalin berkas ke direktori import terlebih dahulu, atau memeriksa pengaturan server.directories.import apabila lokasinya diubah.
  2. Kolom tahunMasuk ditulis tanpa toInteger(). Propertinya tersimpan sebagai string, sehingga perbandingan dan pengurutan angka menyimpang dari nilai bilangannya. Perbaikannya adalah membungkus nilai kolom dengan toInteger() saat penulisan.
  3. Opsi FIELDTERMINATOR terlupa pada berkas non-koma. Seluruh baris menjadi satu kolom, sehingga rujukan kolomnya bernilai NULL tanpa pesan error apa pun. Perbaikannya adalah menuliskan opsi tersebut tepat setelah variabel baris.
  4. CALL { … } IN TRANSACTIONS dijalankan pada Neo4j Browser tanpa awalan :auto. Kueri ditolak karena subquery ini hanya diizinkan pada implicit transaction. Perbaikannya adalah mengawali kueri dengan :auto atau menjalankannya melalui cypher-shell.

Ringkasan#

  • LOAD CSV membaca berkas dari direktori import server melalui URL file:/// dan mengalirkan setiap barisnya ke dalam kueri, sedangkan WITH HEADERS menjadikan baris map berkunci nama kolom.
  • Pola baris-ke-node menuliskan MERGE dengan properti kunci di dalam pola, properti lainnya melalui SET, dan toInteger() untuk mengonversi teks menjadi bilangan.
  • Opsi FIELDTERMINATOR mengganti pemisah koma, sedangkan tanpanya berkas non-koma terbaca satu kolom dan rujukan kolomnya bernilai NULL.
  • CALL (baris) { … } IN TRANSACTIONS OF n ROWS membagi impor besar menjadi transaksi kecil dengan commit antar kelompok, menggantikan USING PERIODIC COMMIT yang sudah dihapus.

Referensi#