Статьи

Neo4j: LOAD CSV — Обработка скрытых массивов в ваших CSV документах

Недавно меня спросили, как обрабатывать «массив» значений внутри столбца в CSV-файле с помощью инструмента LOAD CSV Neo4j, и хотя я изначально думал, что это невозможно, поскольку каждая ячейка рассматривается как строка, Майкл показал мне способ работать вокруг этого, что я думал, было довольно опрятно.
Допустим, у нас есть файл CSV, представляющий людей и их друзей. Это может выглядеть так:

1
2
3
4
name,friends
"Mark","Michael,Peter"
"Michael","Peter,Kenny"
"Kenny","Anders,Michael"

И мы хотим иметь следующие узлы:

  • отметка
  • Майкл
  • Питер
  • Kenny
  • Андерс

И следующие отношения друзей:

  • Марк -> Майкл
  • Марк -> Питер
  • Михаил -> Питер
  • Майкл -> Кенни
  • Кенни -> Андерс
  • Кенни -> Майкл

Мы начнем с загрузки CSV-файла и возврата каждой строки:

1
2
3
4
5
6
7
8
9
$ load csv with headers from "file:/Users/markneedham/Desktop/friends.csv" AS row RETURN row;
+------------------------------------------------+
| row                                            |
+------------------------------------------------+
| {name -> "Mark", friends -> "Michael,Peter"}   |
| {name -> "Michael", friends -> "Peter,Kenny"}  |
| {name -> "Kenny", friends -> "Anders,Michael"} |
+------------------------------------------------+
3 rows

Как и ожидалось, столбец ‘friends’ рассматривается как String, что означает, что мы можем использовать функцию split, чтобы получить массив людей, с которыми мы хотим дружить:

1
2
3
4
5
6
7
8
9
$ load csv with headers from "file:/Users/markneedham/Desktop/friends.csv" AS row RETURN row, split(row.friends, ",") AS friends;
+-----------------------------------------------------------------------+
| row                                            | friends              |
+-----------------------------------------------------------------------+
| {name -> "Mark", friends -> "Michael,Peter"}   | ["Michael","Peter"]  |
| {name -> "Michael", friends -> "Peter,Kenny"}  | ["Peter","Kenny"]    |
| {name -> "Kenny", friends -> "Anders,Michael"} | ["Anders","Michael"] |
+-----------------------------------------------------------------------+
3 rows

Теперь, когда мы получили их как массив, мы можем использовать UNWIND, чтобы получить пары друзей, которых мы хотим создать:

01
02
03
04
05
06
07
08
09
10
11
12
$ load csv with headers from "file:/Users/markneedham/Desktop/friends.csv" AS row WITH row, split(row.friends, ",") AS friends UNWIND friends AS friend RETURN row.name, friend;
+-----------------------+
| row.name  | friend    |
+-----------------------+
| "Mark"    | "Michael" |
| "Mark"    | "Peter"   |
| "Michael" | "Peter"   |
| "Michael" | "Kenny"   |
| "Kenny"   | "Anders"  |
| "Kenny"   | "Michael" |
+-----------------------+
6 rows

А теперь мы представим несколько операторов MERGE для создания соответствующих узлов и отношений:

1
2
3
4
5
6
7
8
9
$ load csv with headers from "file:/Users/markneedham/Desktop/friends.csv" AS row WITH row, split(row.friends, ",") AS friends UNWIND friends AS friend  MERGE (p1:Person {name: row.name}) MERGE (p2:Person {name: friend}) MERGE (p1)-[:FRIENDS_WITH]->(p2);
+-------------------+
| No data returned. |
+-------------------+
Nodes created: 5
Relationships created: 6
Properties set: 5
Labels added: 5
373 ms

А теперь, если мы запросим базу данных, чтобы вернуть все узлы + отношения …

01
02
03
04
05
06
07
08
09
10
11
12
$ match (p1:Person)-[r]->(p2) RETURN p1,r, p2;
+------------------------------------------------------------------------+
| p1                      | r                  | p2                      |
+------------------------------------------------------------------------+
| Node[0]{name:"Mark"}    | :FRIENDS_WITH[0]{} | Node[1]{name:"Michael"} |
| Node[0]{name:"Mark"}    | :FRIENDS_WITH[1]{} | Node[2]{name:"Peter"}   |
| Node[1]{name:"Michael"} | :FRIENDS_WITH[2]{} | Node[2]{name:"Peter"}   |
| Node[1]{name:"Michael"} | :FRIENDS_WITH[3]{} | Node[3]{name:"Kenny"}   |
| Node[3]{name:"Kenny"}   | :FRIENDS_WITH[4]{} | Node[4]{name:"Anders"}  |
| Node[3]{name:"Kenny"}   | :FRIENDS_WITH[5]{} | Node[1]{name:"Michael"} |
+------------------------------------------------------------------------+
6 rows

… вы увидите, что у нас есть все. Если вместо списка людей через запятую у нас есть буквенный массив в ячейке …

1
2
3
4
name,friends
"Mark", "[Michael,Peter]"
"Michael", "[Peter,Kenny]"
"Kenny", "[Anders,Michael]"

… Нам нужно настроить часть запроса, которая извлекает наших друзей, чтобы убрать первый и последний символы:

1
2
3
4
5
6
7
8
9
$ load csv with headers from "file:/Users/markneedham/Desktop/friendsa.csv" AS row RETURN row, split(substring(row.friends, 1, length(row.friends) -2), ",") AS friends;
+-------------------------------------------------------------------------+
| row                                              | friends              |
+-------------------------------------------------------------------------+
| {name -> "Mark", friends -> "[Michael,Peter]"}   | ["Michael","Peter"]  |
| {name -> "Michael", friends -> "[Peter,Kenny]"}  | ["Peter","Kenny"]    |
| {name -> "Kenny", friends -> "[Anders,Michael]"} | ["Anders","Michael"] |
+-------------------------------------------------------------------------+
3 rows

И затем, если мы объединим весь запрос, мы получим следующее:

1
2
3
4
5
6
7
8
$ load csv with headers from "file:/Users/markneedham/Desktop/friendsa.csv" AS row WITH row, split(substring(row.friends, 1, length(row.friends) -2), ",") AS friends UNWIND friends AS friend  MERGE (p1:Person {name: row.name}) MERGE (p2:Person {name: friend}) MERGE (p1)-[:FRIENDS_WITH]->(p2);;
+-------------------+
| No data returned. |
+-------------------+
Nodes created: 5
Relationships created: 6
Properties set: 5
Labels added: 5