Importing data
|
This section documents the current Neo4j This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, as Neo4j iterates quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
Neo4j offers two very different ways to get data in: a transactional LOAD CSV for ongoing or
moderate-sized loads against a running, online database, and an offline bulk loader for populating a
new database (or adding to one) as fast as possible from flat files. Which one applies depends on
whether the database needs to stay up and queryable while the data lands.
LOAD CSV with CALL \{ … \} IN TRANSACTIONS
LOAD CSV streams rows from a CSV file (local or over HTTP/HTTPS/S3) one at a time into a Cypher
query, with WITH HEADERS exposing each column as a map key. Older guidance wrapped a whole LOAD CSV
in a single implicit transaction and used USING PERIODIC COMMIT to commit every N rows so the
transaction state would not grow unbounded — that clause was removed in Neo4j 5. The modern
replacement wraps the per-row work in CALL \{ … \} IN TRANSACTIONS, which batches the inner unit of
work into its own sub-transactions committed every rows rows:
// people.csv: personName,movieTitle,releaseYear,role
LOAD CSV WITH HEADERS FROM 'file:///people.csv' AS row
CALL {
WITH row
MERGE (p:Person {name: row.personName})
MERGE (m:Movie {title: row.movieTitle})
ON CREATE SET m.releaseYear = toInteger(row.releaseYear)
MERGE (p)-[a:ACTED_IN]->(m)
ON CREATE SET a.role = row.role
} IN TRANSACTIONS OF 500 ROWS
Each batch of 500 rows commits independently, so a failure partway through leaves earlier batches
durable rather than rolling back the whole file — the trade-off is that the load as a whole is no
longer atomic. CALL \{ … \} IN TRANSACTIONS can only appear as a top-level clause (not nested inside
another transactional construct), and every variable the inner block needs — row here — must be
passed in explicitly with WITH. Add ON ERROR CONTINUE to skip a batch that fails validation instead
of aborting the whole load, and REPORT STATUS AS status to collect per-batch outcomes into a column
for inspection.
Before loading, MERGE on a label needs a matching constraint or index or every MERGE becomes a
label-scan; create it first:
CREATE CONSTRAINT person_name IF NOT EXISTS FOR (p:Person) REQUIRE p.name IS UNIQUE;
CREATE CONSTRAINT movie_title IF NOT EXISTS FOR (m:Movie) REQUIRE m.title IS UNIQUE;
LOAD CSV is the right tool for incremental loads, upserts (MERGE), and files up to a few million
rows against a database that other clients are still using concurrently. See
LOAD CSV for the full clause reference,
including headerless files, custom field terminators, and remote URLs.
Bulk offline import and external sources
For an initial load of tens of millions of rows or more, LOAD CSV is far slower than writing the
store files directly. neo4j-admin database import builds a new database’s files from CSV, bypassing
the transaction log and query engine entirely — the target database must not exist yet (for full)
and the DBMS must be offline for that database while the import runs.
# Full import: builds a brand-new database from scratch (DBMS must be stopped, or the
# target database must not exist).
neo4j-admin database import full moviedb \
--nodes=Person=people.csv \
--nodes=Movie=movies.csv \
--relationships=ACTED_IN=roles.csv \
--delimiter=","
# Incremental import: adds nodes/relationships/properties from more CSV files into an
# EXISTING database created by neo4j-admin, without a full reload.
neo4j-admin database import incremental moviedb \
--force \
--nodes=Person=new_people.csv \
--relationships=ACTED_IN=new_roles.csv
The CSV headers for neo4j-admin import carry Neo4j’s own type and role syntax directly
(:ID, :LABEL, :START_ID, :END_ID, :TYPE, name:string, …), which is a different header
convention from the plain-column CSV that LOAD CSV reads. incremental runs in stages and can
resume a partially completed run with --stage=<name> if it is interrupted. For a GUI alternative on
a single desktop instance, Neo4j Desktop's Data Importer tool maps CSV columns to node labels,
relationship types and properties visually and drives the same import machinery underneath — useful
for a one-off load without hand-writing the header syntax.
Neither tool reaches into another live system. To pull rows from a REST API, a JSON file, or a
relational database directly inside Cypher, use the APOC procedures apoc.load.json (a URL or file
path, walked as a stream of maps) and apoc.load.jdbc (a JDBC connection string and SQL query,
streamed row by row) — both are typically driven through the same CALL \{ … \} IN TRANSACTIONS
batching shown above rather than loaded as one giant transaction. See
APOC and extensions for setup and more procedures.
Reference: Import (overview of both import paths), neo4j-admin import tutorial (worked walkthrough of the header syntax and multi-file imports), and CSV import (CSV shape and encoding requirements shared by both tools).
Choosing an approach
| Tool | Database state | Best for |
|---|---|---|
|
Online, concurrently used |
Incremental loads, upserts, small-to-moderate files |
|
Offline, database must not exist |
Very large initial bulk loads |
|
Offline, database already exists |
Very large follow-up bulk loads |
|
Online |
Pulling from an external API, file, or relational database |
The same online-vs-offline split exists elsewhere: MongoDB separates ongoing writes from bulk tooling
like mongoimport (see MongoDB schema design), and a
relational engine separates row-by-row INSERT from bulk-loading utilities such as COPY or
LOAD DATA (see SQL reference) — in every case, bypassing the normal
write path trades transactional safety and concurrent access for raw load throughput.