Higienitas impor

Deduplikasi dengan MERGE, constraint pendukung, dan nilai default defensif.

Daftar isi

Berkas CSV yang diimpor jarang datang bersih, dan Neo4j tidak menolak data cacat dengan sendirinya. Topik ini membahas tiga lapis pengamanan pada pipeline impor, yaitu rangkaian tahap berkas mentah hingga graf siap pakai. Lapis pertama adalah constraint keunikan yang mendukung MERGE, lapis kedua penanganan baris duplikat, dan lapis ketiga pembersihan nilai kosong, spasi berlebih, serta tipe yang salah dengan trim, toInteger, toFloat, coalesce, dan CASE. Seluruh contoh dijalankan pada server Neo4j 2026.10 dengan berkas pegawai-kotor.csv yang sengaja kotor. Setelah topik ini dibaca, impor dapat dijalankan ulang tanpa menciptakan node kembar dan hasilnya dapat diperiksa dengan kueri validasi yang tetap.

Constraint keunikan pendukung MERGE#

MERGE bekerja dengan pola get-or-create, yaitu mencari dahulu, lalu membentuk node baru apabila pencariannya kosong. Constraint keunikan pada properti kunci memberikan dua jaminan sekaligus bagi proses impor. Basis data menolak penulisan yang menghasilkan dua node dengan nilai kunci sama, sehingga kekeliruan kueri tidak berubah menjadi data kembar yang masuk tanpa pesan. Constraint juga membawa index, yaitu struktur data yang mempercepat pencarian kunci pada setiap baris berkas.

Constraint dibuat terlebih dahulu, sebelum berkas mana pun diimpor. Kueri berikut membuat constraint keunikan untuk properti nama pada label :Karyawan, lalu menampilkannya dengan filter nama agar keluarannya stabil. Bentuk IF NOT EXISTS membuat pernyataan ini idempoten, yaitu aman dijalankan ulang. Penanda _m5 pada namanya menandai aset milik modul ini.

cypher
CREATE CONSTRAINT karyawan_nama_m5 IF NOT EXISTS
FOR (k:Karyawan) REQUIRE k.nama IS UNIQUE;
SHOW CONSTRAINTS YIELD name, type, labelsOrTypes, properties
WHERE name = "karyawan_nama_m5"
RETURN name, type, labelsOrTypes, properties;
text Keluaran
name, type, labelsOrTypes, properties
"karyawan_nama_m5", "NODE_PROPERTY_UNIQUENESS", ["Karyawan"], ["nama"]

Keluaran menegaskan tipe NODE_PROPERTY_UNIQUENESS yang kini mengawasi properti nama, sehingga setiap MERGE dengan kunci nama berjalan di atas jaminan keunikan yang ditegakkan basis data, bukan sekadar konvensi penulisan kueri.

Berkas contoh dengan cacat data#

Berkas pegawai-kotor.csv mewakili hasil ekspor dari sistem lain yang belum melalui pemeriksaan apa pun. Berkas ini memuat satu baris header dan enam baris data dengan empat cacat yang sengaja ditanam.

  • Baris Eri muncul dua kali dengan isi yang sama persis, mewakili duplikasi baris pada sumber data.
  • Baris Fajar memuat spasi pada kedua sisi nama, yaitu kolom yang menjadi kunci MERGE, dan kolom tahunMasuk kosong.
  • Baris Gilang menuliskan tahun sebagai teks “dua ribu” yang bukan angka.
  • Baris Hana memuat spasi di awal nilai tahunMasuk.
csv pegawai-kotor.csv
nama,divisi,tahunMasuk
Agus,Infra,2019
Eri,Infra,2020
Eri,Infra,2020
 Fajar ,Data,
Gilang,Produk,dua ribu
Hana,Data, 2023

Setiap kelas cacat menuntut penanganan yang berbeda pada bagian-bagian berikutnya. Kolom tahunMasuk digunakan sebagai contoh nilai yang harus menjadi bilangan, sedangkan kolom nama dan divisi mewakili teks yang mungkin mengandung spasi. Isi berkas diperiksa melalui terminal sebelum digunakan, dan salinannya wajib berada pada direktori import server sebelum dibaca LOAD CSV. Kueri impor selanjutnya membaca salinan itu melalui URL file:///pegawai-kotor.csv.

bash
# menampilkan isi berkas contoh dari direktori kerja
head pegawai-kotor.csv
text Keluaran
nama,divisi,tahunMasuk
Agus,Infra,2019
Eri,Infra,2020
Eri,Infra,2020
 Fajar ,Data,
Gilang,Produk,dua ribu
Hana,Data, 2023
bash
# tidak-dijalankan
# menyalin berkas contoh ke direktori import server Neo4j modul ini
cp pegawai-kotor.csv /home/kal/my-project/neo4j/inst-m1/import/

Deduplikasi baris duplikat dengan MERGE#

Setiap baris diubah menjadi node :Karyawan dengan nama sebagai kunci pencocokan, sedangkan divisi dan tahun masuk dituliskan melalui SET. Fungsi trim dipanggil pada kolom nama sejak kueri pertama, karena spasi yang tersimpan pada kunci membuat penulisan yang tampak sama menjadi dua kunci yang berbeda, sehingga MERGE membentuk node kembar. Kolom tahun masuk melewati toInteger karena LOAD CSV menyimpan nilai CSV sebagai string.

cypher
LOAD CSV WITH HEADERS FROM 'file:///pegawai-kotor.csv' AS baris
MERGE (k:Karyawan {nama: trim(baris.nama)})
SET k.divisi = baris.divisi,
    k.tahunMasuk = toInteger(baris.tahunMasuk);
MATCH (k:Karyawan)
RETURN count(k) AS jumlahKaryawan;
text Keluaran
jumlahKaryawan
8

Angka delapan berasal dari lima node :Karyawan lama pada graf modul ini ditambah tiga node baru. Enam baris data berubah menjadi tiga node karena baris Eri yang ganda menemukan node yang sama pada kedua kemunculannya. Kueri yang sama dijalankan ulang dan keluarannya tetap delapan, menandakan pola ini idempoten. Daftar nama berikut memperlihatkan keadaan tiap node.

cypher
MATCH (k:Karyawan)
WHERE k.nama IN ['Agus', 'Eri', 'Fajar', 'Gilang', 'Hana']
RETURN k.nama AS nama, k.divisi AS divisi, k.tahunMasuk AS tahunMasuk
ORDER BY nama;
text Keluaran
nama, divisi, tahunMasuk
"Agus", "Infra", 2019
"Eri", "Infra", 2020
"Fajar", "Data", NULL
"Gilang", "Produk", NULL
"Hana", "Data", 2023

Keluaran memperlihatkan dua kekosongan tanpa pesan error apa pun. Fajar dan Gilang kehilangan properti tahunMasuk karena toInteger mengembalikan null untuk string kosong maupun teks yang bukan angka, sedangkan SET dengan null menghapus properti dari node.

Nilai kosong dan nilai default defensif#

Nilai kosong dan kegagalan konversi diberi perlakuan tetap, yaitu ditandai dengan nilai default yang disepakati alih-alih dibiarkan menghilang. Pada kueri berikut, CASE memeriksa kolom tahunMasuk setelah dibersihkan dengan trim. Kolom yang kosong dituliskan sebagai 0, sedangkan kolom berisi teks lain melewati toInteger yang dijaga coalesce. Apabila konversi gagal dan menghasilkan null, coalesce menggantinya dengan 0. Nilai 0 bukan tahun yang sah, melainkan penanda nilai yang belum valid, sehingga mudah dicari saat audit.

cypher
LOAD CSV WITH HEADERS FROM 'file:///pegawai-kotor.csv' AS baris
MERGE (k:Karyawan {nama: trim(baris.nama)})
SET k.divisi = trim(baris.divisi),
    k.tahunMasuk = CASE
      WHEN trim(baris.tahunMasuk) = '' THEN 0
      ELSE coalesce(toInteger(baris.tahunMasuk), 0)
    END;
MATCH (k:Karyawan)
WHERE k.nama IN ['Fajar', 'Gilang', 'Hana']
RETURN k.nama AS nama, k.tahunMasuk AS tahunMasuk
ORDER BY nama;
text Keluaran
nama, tahunMasuk
"Fajar", 0
"Gilang", 0
"Hana", 2023

Fajar menyimpan penanda 0 karena kolomnya kosong, Gilang menyimpan penanda yang sama karena teksnya gagal dikonversi, dan Hana menyimpan 2023 karena spasi di awal nilai tidak mengganggu konversi. Impor ulang ini aman dijalankan karena MERGE menemukan node yang sudah ada, sehingga penulisan ulang properti terjadi pada node yang sama. trim pada kolom divisi ikut diterapkan sebagai kebiasaan defensif. Penanda lain dapat digunakan asalkan nilainya bukan nilai data yang sah.

Memperbaiki tipe yang salah saat impor#

Berkas CSV tidak menyimpan tipe data, sehingga keputusan tipe seluruhnya berada pada kueri impor. Fungsi toInteger mengubah string menjadi bilangan bulat, sedangkan toFloat menangani nilai desimal semacam berat atau skor. Keduanya mengembalikan null ketika konversi tidak mungkin dilakukan, bukan error. Perilakunya dapat diperiksa dengan daftar nilai uji melalui UNWIND.

cypher
UNWIND ['2023', ' 2023', '', 'dua ribu'] AS s
RETURN s AS teks, toInteger(s) AS hasil
ORDER BY teks;
text Keluaran
teks, hasil
"", NULL
" 2023", 2023
"2023", 2023
"dua ribu", NULL

Spasi di awal maupun akhir nilai ditoleransi, sehingga " 2023" tetap menjadi 2023, sedangkan string kosong dan teks yang bukan angka menghasilkan null. Karena spasi ditoleransi oleh konversi angka, trim tetap diperlukan bukan untuk toInteger, melainkan untuk kunci pencocokan dan perbandingan teks. toFloat berperilaku sama pada ranah desimalnya.

cypher
UNWIND ['4.75', '4,75', 'empat'] AS s
RETURN s AS teks, toFloat(s) AS hasil
ORDER BY teks;
text Keluaran
teks, hasil
"4,75", NULL
"4.75", 4.75
"empat", NULL

Nilai dengan tanda koma sebagai pemisah desimal tidak diterima, sebagaimana terlihat pada “4,75” yang menghasilkan null. Apabila sumber data menggunakan koma, koma diganti dengan titik melalui replace sebelum toFloat dipanggil. Penjaganya tetap sama, yaitu coalesce yang memberi nilai default apabila hasil konversinya null.

Validasi pascaimpor#

Impor yang selesai belum berarti benar, sehingga hasilnya diperiksa dengan kueri yang tetap. Pemeriksaan pertama menghitung jumlah node dan jumlah relasi pada label modul ini. Angka pembandingnya dinyatakan lebih dahulu, karena selisih yang tidak terjelaskan menandakan impor yang berjalan sebagian atau berulang.

cypher
MATCH (k:Karyawan)
RETURN count(k) AS jumlahKaryawan;
text Keluaran
jumlahKaryawan
8
cypher
MATCH (:Karyawan)-[r:ANGGOTA_DARI]->(:Divisi)
RETURN count(r) AS jumlahRelasi;
text Keluaran
jumlahRelasi
5

Node dengan penanda 0 atau properti yang hilang dilaporkan namanya, sehingga baris bermasalah dapat ditelusuri kembali.

cypher
MATCH (k:Karyawan)
WHERE k.tahunMasuk = 0 OR k.tahunMasuk IS NULL
RETURN k.nama AS nama, k.tahunMasuk AS tahunMasuk
ORDER BY nama;
text Keluaran
nama, tahunMasuk
"Fajar", 0
"Gilang", 0

Pemeriksaan ketiga mencari node tanpa relasi, yaitu node yang berdiri sendiri karena tahap pembentukan relasinya belum berjalan atau gagal.

cypher
MATCH (k:Karyawan)
WHERE NOT (k)--()
RETURN k.nama AS nama
ORDER BY nama;
text Keluaran
nama
"Fajar"
"Gilang"
"Hana"

Keluaran terakhir menemukan ketiga node baru tanpa relasi. Temuan ini sesuai keadaannya karena impor berkas kotor hanya membentuk node, sedangkan relasi :ANGGOTA_DARI perlu dibentuk pada tahap tersendiri. Validasi menandai pekerjaan yang belum selesai sebelum data digunakan lebih lanjut.

Kesalahan umum#

Empat kesalahan berikut paling sering muncul saat topik ini dipraktikkan.

  1. Spasi pada kolom kunci tidak dibersihkan. Dua penulisan nama yang tampak sama menjadi dua kunci yang berbeda, sehingga MERGE membentuk node kembar tanpa pesan error. Perbaikannya adalah memanggil trim pada kolom kunci dan menghapus node kembar yang sudah terbentuk.
  2. Konversi yang gagal dianggap akan menghentikan impor dengan error. toInteger dan toFloat justru mengembalikan null, dan SET dengan null menghapus propertinya dalam senyap. Perbaikannya adalah membungkus konversi dengan coalesce dan menandai hasilnya dengan nilai default.
  3. Nilai desimal dengan pemisah koma diberikan langsung kepada toFloat. Nilai “4,75” menghasilkan null karena pemisah desimal yang diterima adalah titik. Perbaikannya adalah mengganti koma dengan titik melalui replace sebelum konversi.
  4. Constraint keunikan dibuat setelah data duplikat masuk. Pembuatan constraint ditolak basis data karena data yang sudah ada melanggar aturan yang hendak ditegakkan. Perbaikannya adalah menghapus duplikat terlebih dahulu, kemudian membuat constraint tersebut.

Ringkasan#

  • Constraint keunikan pada properti kunci dibuat sebelum impor melalui CREATE CONSTRAINT IF NOT EXISTS, sehingga MERGE mendapatkan pencarian kunci yang cepat dan keunikan yang ditegakkan.
  • MERGE mencegah node kembar pada baris duplikat, sedangkan trim menjaga dua penulisan yang tampak sama tetap terbaca sebagai satu kunci.
  • Nilai kosong dan konversi yang gagal ditangani dengan CASE dan coalesce menuju nilai default penanda, sedangkan toInteger serta toFloat mengubah string menjadi angka atau null saat konversi mustahil.
  • Validasi pascaimpor menghitung node dan relasi per label, mencari nilai penanda beserta properti yang hilang, dan melaporkan node tanpa relasi.

Referensi#