Importing data with LOAD CSV

Neo4j · Practical · 14 min

Most graphs start as tables somewhere, and a correct, batched import is the first real project task.

LOAD CSV reads a CSV file row by row, from file:/// (the server's import directory) or an https:// URL. With WITH HEADERS each row is a map keyed by column name; without it, a list.

Every value arrives as a string. Convert explicitly: toInteger(row.age), toFloat, date(row.joined), split(row.tags, ';'). Empty fields become null; coalesce supplies defaults.

A robust import follows a pattern:

  • create constraints first, so MERGE uses an index instead of scanning
  • load nodes in one pass with MERGE on the key, then relationships in a second pass that MATCHes both ends
  • for large files, batch with CALL (row) { ... } IN TRANSACTIONS OF 10000 ROWS so each batch commits separately instead of building one gigantic transaction

Batched subqueries must run in an auto-commit (implicit) transaction; in Neo4j Browser prefix the query with :auto, and from drivers use session.run. Add ON ERROR CONTINUE or REPORT STATUS to control failures per batch.

For tens of millions of rows into a new database, the offline neo4j-admin database import tool is far faster. The older CALL { WITH row ... } importing-WITH form still works but is deprecated in favour of CALL (row) { ... }.

Example

// people.csv:  email,name,born
// follows.csv: from,to,since
CREATE CONSTRAINT person_email IF NOT EXISTS
FOR (p:Person) REQUIRE p.email IS UNIQUE;

LOAD CSV WITH HEADERS FROM 'file:///people.csv' AS row
CALL (row) {
  MERGE (p:Person {email: row.email})
  SET p.name = row.name, p.born = toInteger(row.born)
} IN TRANSACTIONS OF 10000 ROWS;

LOAD CSV WITH HEADERS FROM 'file:///follows.csv' AS row
CALL (row) {
  MATCH (a:Person {email: row.from}), (b:Person {email: row.to})
  MERGE (a)-[f:FOLLOWS]->(b)
  SET f.since = toInteger(row.since)
} IN TRANSACTIONS OF 10000 ROWS;

Remember

  • All CSV values are strings: convert with toInteger, date, split
  • Constraints first, then nodes, then relationships
  • CALL (row) { ... } IN TRANSACTIONS batches large imports
  • Use neo4j-admin import for huge initial loads

Watch out for

  • Importing without a constraint on the MERGE key, making each row scan every node of the label.

Check yourself

  1. A CSV column age contains '42'. What is row.age in LOAD CSV?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: neo4j.com/docs/cypher-manual/current/clauses/load-csv/

Builds on

Unlocks

Nothing builds on this yet.