neo4jについてまとめてみた

## 自己紹介
初めまして。加藤です。
名古屋の大学で情報系について勉強しています。
neo4jというグラフデータベースに触れる機会があったのでQiitaの練習がてらまとめてみました。
## neo4j について

### neo4j とは

データやデータ間の関係がデータレコードごとに定義されるネイティブグラフDBです。

neo4jではcypherというグラフクエリ言語を用いてデータを操作します。

### グラフDB とは

テーブル単位でデータ間の関係を持つ従来のリレーショナルデータベースと違い、データ単位でデータ間の関係を持つネットワーク状のデータベースのことです。

### neo4j のメリット

- RDBではデータ間の関係をテーブルごとに定義された外部キーを参照しデータを操作したが、グラフDBではデータ間の関係(エッジ)がデータ(ノード)ごとに明示的に定義されているためエッジを辿るだけでよく、RDBで大量のJOIN文を用いて処理したデータでも計算が早い(join bombが発生しない)。

- RDBではリレーションを考える必要があったが、グラフDBでは考えなくて良いので設計が簡単(な場合が多い)。

##### 例

  - cypher(深さ無制限)

  ```cypher
  MATCH (p:Product)-[:CATEGORY]->(l:ProductCategory)-[:PARENT*0..]->(:ProductCategory {name:"Dairy Products"})
  RETURN p.name
  ```

  - SQL(深さ3)

  ```sql
  SELECT p.ProductName
  FROM Product AS p
  JOIN ProductCategory pc ON (p.CategoryID = pc.CategoryID AND pc.CategoryName = "Dairy Products")
  
  JOIN ProductCategory pc1 ON (p.CategoryID = pc1.CategoryID)
  JOIN ProductCategory pc2 ON (pc1.ParentID = pc2.CategoryID AND pc2.CategoryName = "Dairy Products")
  
  JOIN ProductCategory pc3 ON (p.CategoryID = pc3.CategoryID)
  JOIN ProductCategory pc4 ON (pc3.ParentID = pc4.CategoryID)
  JOIN ProductCategory pc5 ON (pc4.ParentID = pc5.CategoryID AND pc5.CategoryName = "Dairy Products")
  
  ```

- 隣接ノードへのポインタを物理的に保持しているため隣接ノードを辿る計算量が $\mathcal{O}(1)$ (index-free-adjency)

- .Net、Java、JavaScript、Go、Pythonのドライバーを公式にサポートしている。

- コミュニティーが大規模である。

- 柔軟な構造のため、機能の追加などが容易。

### neo4j のデメリット

- DBへの書き込みが1台のノードに制限されているため、大量のデータの書き込みには向かない。
- グラフをメモリ上にロードすることができるが、システムの耐久性の低下やメモリが足りない場合にパフォーマンスが落ちる。

## neo4j を触ってみる

### neo4j をdockerで立ち上げる

docker-composeを使ってneo4jを立ち上げるので、以下のようにdocker-compose.ymlを記述しましょう。

````yaml
version: "3"

services:
  neo4j:
    image: neo4j:latest
    container_name: neo4j
    ports:
      - 7474:7474
      - 7687:7687
    volumes:
      - $PWD/data:/data
      - $PWD/logs:/logs
````

docker-compose upで起動します(結構時間がかかる)。

```bash
% docker-compose up
Pulling neo4j (neo4j:latest)...
6f28985ad184: Pull complete
b3555c388585: Pull complete
42965fccf401: Pull complete
55a08f332394: Pull complete
777621e154b8: Pull complete
e0588cf29830: Pull complete
3f2ffef95d5c: Pull complete
de6bf150b1fd: Pull complete
Digest: sha256:e9cce6d1bd2289e6deab711a2e8930ea6105d3274bfbbd2a84a5b5b3b0036878
Status: Downloaded newer image for neo4j:latest
Creating neo4j ... done
Attaching to neo4j
neo4j    | Warning: Folder mounted to "/logs" is not writable from inside container. Changing folder owner to neo4j.
neo4j    | Warning: Folder mounted to "/data" is not writable from inside container. Changing folder owner to neo4j.
neo4j    | Directories in use:
neo4j    |   home:         /var/lib/neo4j
neo4j    |   config:       /var/lib/neo4j/conf
neo4j    |   logs:         /logs
neo4j    |   plugins:      /var/lib/neo4j/plugins
neo4j    |   import:       /var/lib/neo4j/import
neo4j    |   data:         /var/lib/neo4j/data
neo4j    |   certificates: /var/lib/neo4j/certificates
neo4j    |   run:          /var/lib/neo4j/run
neo4j    | Starting Neo4j.
neo4j    | 2021-03-24 07:00:00.465+0000 INFO  Starting...
neo4j    | 2021-03-24 07:00:05.648+0000 INFO  ======== Neo4j 4.2.4 ========
neo4j    | 2021-03-24 07:00:10.786+0000 INFO  Initializing system graph model for component 'security-users' with version -1 and status UNINITIALIZED
neo4j    | 2021-03-24 07:00:10.835+0000 INFO  Setting up initial user from defaults: neo4j
neo4j    | 2021-03-24 07:00:10.841+0000 INFO  Creating new user 'neo4j' (passwordChangeRequired=true, suspended=false)
neo4j    | 2021-03-24 07:00:10.893+0000 INFO  Setting version for 'security-users' to 2
neo4j    | 2021-03-24 07:00:10.922+0000 INFO  After initialization of system graph model component 'security-users' have version 2 and status CURRENT
neo4j    | 2021-03-24 07:00:10.951+0000 INFO  Performing postInitialization step for component 'security-users' with version 2 and status CURRENT
neo4j    | 2021-03-24 07:00:11.970+0000 INFO  Bolt enabled on 0.0.0.0:7687.
neo4j    | 2021-03-24 07:00:14.868+0000 INFO  Remote interface available at http://localhost:7474/
neo4j    | 2021-03-24 07:00:14.877+0000 INFO  Started.
```

### neo4j を開く

起動したら、ブラウザを開いてlocalhost:7474にアクセスしてみましょう(もしここでページが空白だったらキャッシュを消せば治るはず)。

アクセスすると以下のページが表示されます。

![スクリーンショット 2021-03-24 16.18.58（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1571667/7a9fb611-b68b-c0ec-7a63-04a63d5f89fe.png)

UsernameとPasswordを入力し、Connectをクリック。

```
Username: neo4j
Password: neo4j
```

Passwordの変更が求められるので変更してください。

### Cypherクエリを触ってみる

グラフDBの世界ではノードとリレーションという概念が存在します。

#### ノードとは

ノードとは個々のデータを表すもので、「ラベル」と「プロパティ」という概念を持ちます。

ラベルとはそのノードが何であるか(どのグループに属すか)を表すRDBでいうテーブルのようなものです。

プロパティはそのノードが実際に保持する情報を表現します。

Cypherクエリではノードは以下のように記述します。

```cypher
() #ラベルもプロパティも指定しないノード
(:User) #ラベルを付与したノード
(:User{name:"kato", email:"kato@xxx.com"}) #ラベルとプロパティを付与したノード
```

#### ノードを作成

ノードを作成するには*CREATE*句を使います。以下のCypher クエリを実行してみましょう。

```cypher
CREATE (:User{name:"kato", email:"kato@xxx.com"}) 
```

実行したら以下のように出力されます。

![スクリーンショット 2021-03-26 13.51.14（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1571667/6f1ee082-fcf7-6bbb-e05e-361924ade1b8.png)


無事追加できていることが確認できました。しかし、これではデータの作成の際にどんなデータが入ったのか分かりづらいですよね。ここで活躍するのが「変数」です。変数にはノードやリレーションのデータを格納することができます。実際に変数を使って以下のように打ち込んでみましょう。

```cypher
CREATE (u:User{name:"kato", email:"kato@xxx.com"}) 
RETURN u
```

ここでは、変数uに作成するノードの情報を格納し、RETURN uでその情報を表示しています。

実行すると以下のようになります。

![スクリーンショット 2021-03-26 13.58.09（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1571667/42cd43f2-d275-1243-1798-d3e948a77ee7.png)


これで追加したノードを出力することができました。

#### ノードを検索

ノードの検索には*MATCH*句を使います。以下のクエリを実行してみましょう。

```cypher
MATCH (u:User) RETURN u
```

このクエリでは:Userラベルがついているデータを変数uに格納し、それを表示しています。

実行すると以下のようになるはずです。

![スクリーンショット 2021-03-26 14.06.52（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1571667/8badc728-7197-9bac-5677-795aa7e39912.png)


これで無事検索することができました。

#### ノードを更新する

ノードの更新には*SET*句を使います。以下のクエリを実行してみましょう。

```cypher
MATCH (u:User{name:"kato"})
SET
	u.name="sato",
	u.email="sato@xxx.com"
RETURN
	u
```

ここでは、Userラベルのついたノードからプロパティを指定して、そのノードに対してノードを更新しています。

実行すると以下のようになります。

![スクリーンショット 2021-03-26 14.33.01（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1571667/40201e9f-a89d-3337-5f32-e6dd26067deb.png)


これでname属性にkatoを持つノードが更新できました。

#### ノードを削除する

ノードの削除には*DELETE*句を使います。以下のクエリを実行してみましょう。

```cypher
MATCH (u:User{name:"sato"}) DELETE u
```

ここでは、Userラベルのついたノードのうちname属性がsatoであるものを削除しています。

実行すると以下のようになります。

![スクリーンショット 2021-03-26 14.38.59（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1571667/1f939f5d-fe67-07fc-8493-b61a7b1aa0b4.png)


これで、ノードを削除することができました。



次に、リレーションについてもCRUD処理をしてみます。

#### リレーションとは

リレーションとは、ノード間の関係を表し、ノードと同じようにラベルとプロパティを持ちます。

リレーションはCypherクエリでは以下のように表します。

```cypher
[] #ラベルもプロパティも指定しないリレーション
[:MEMBER_OF] #ラベルを付与したリレーション
[:MEMBER_OF{since:"xxxx"}] #ラベルとプロパティを付与したリレーション
```

#### リレーションの作成

リレーションはそれ単体では存在することができないので、ノードと一緒に作成します。

```cypher
CREATE (u:User{name:"kato", email:"kato@xxx.com"}) -[r:MEMBER_OF{since:"xxxx"}]-> (c:Club{name:"xxxClub"})
RETURN u, r, c
```

リレーションの方向は-, ->, <-で表すことができます。これはkatoさんはxxxClubのメンバーであることを表しています。

実行すると以下のようになります。

![スクリーンショット 2021-03-26 14.55.56（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1571667/fd5f5cf0-958a-753c-bbf4-348104e0d207.png)


無事にリレーションが作成できています。

#### リレーションの検索

何かしらのノードに対応するリレーションを検索する場合、以下のようにして検索します。

```cypher
MATCH (n1)-[r:MEMBER_OF]->(n2) RETURN r, n1, n2
```

これは、全てのノードからリレーションにMEMBER_OFのラベルを持つものを表示します。

実行すると以下のようになります。

![スクリーンショット 2021-03-26 15.08.41（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1571667/1f62c5a5-2c5b-4a7a-4a99-b22227577d9f.png)


#### リレーションを更新する

リレーションの更新はリレーションを削除して作り直します。

#### リレーションを削除する

リレーションの削除も、ノードと同じようにしてできます。

```cypher
MATCH (:User{name:"kato"}) -[r:MEMBER_OF]-> ()
DELETE r
```

ここでは、Userラベルのついたノードのうち、nameプロパティがkatoであるものに対して、MEMBER_OFのラベルを持つリレーションを全て消去しています。

実行するとリレーションが削除されます。



#### Tips

全てのノードを検索する場合、ラベルやプロパティを指定しないことで検索できます。

```cypher
MATCH (n) RETURN n
```

実行結果は以下になります。

![スクリーンショット 2021-03-26 15.26.03（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1571667/35512dcf-b7c3-d9b5-9981-942f9aeca3b1.png)


また、これを用いて全てのノードを削除することができます。

```cypher
MATCH (n) DELETE n
```

しかし、リレーションが存在する場合にはこれだけでは削除できないので以下のようにして削除します。

```cypher
MATCH (n) DETACH DELETE n
```



## 実践

### AwesomePublicDatasetsをダウンロード

https://www.kaggle.com/startupsci/awesome-datasets-graph からデータセットをダウンロードします。

ダウンロードしたデータセットのうち、csvファイル3つをneo4j-sample/datasets/AwesomePublicDatasets/に入れます。

#### ノードの追加

出来たらそれぞれのノードを追加します。csvからのノードの追加はLOAD CSVモジュールを使います。今回はヘッダー付きのcsvなのでLOAD CSV WITH HEADERS で読み込みます。

```cypher
LOAD CSV WITH HEADERS FROM "file:///AwesomePublicDataset/datasources.csv" AS line
CREATE (:DataSource { name: line.datasourceName, about: line.about, link: line.link, tool: line.tool, categoryName: line.categoryName, vintage: line.vintage})
```

```cypher
LOAD CSV WITH HEADERS FROM "file:///AwesomePublicDataset/datasets.csv" AS line
CREATE (:DataSet { name: line.datasetName, about: line.about, link: line.link, categoryName: line.categoryName,cloud: line.cloud, vintage: line.vintage})
```

```cypher
LOAD CSV WITH HEADERS FROM "file:///AwesomePublicDataset/categories.csv" AS line
CREATE (:Category { name: line.categoryName})
```

#### リレーションの追加

ノードを追加したらリレーションを追加します。

##### Dataset PART_OF Category 

```cypher
MATCH (c:Category), (dset:DataSet)
WHERE c.name=dset.categoryName
CREATE (dset) -[:PART_OF]-> (c)
```

##### DataSource PART_OF Category

```cypher
MATCH (c:Category), (dsource:DataSource)
WHERE c.name=dsource.categoryName
CREATE (dsource) -[:PART_OF]-> (c)
```

cypherクエリではSQLと同じようにWHERE句を使うことができます。ここでは、Categoryラベルのついたノードのname属性とDataSourceラベルのついたノードのctegoryName属性が等しいノード間を、リレーションでつないでいます。

#### ノードを見てみる

無事インポートできたので、Biologyカテゴリに関連のあるノードを見てみましょう。

```cypher
MATCH (n) -[p]-> (c:Category{name:"Biology"})
RETURN n, c, p
```

実行すると以下のようになります。

![スクリーンショット 2021-03-26 15.53.46（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1571667/3a4413cb-8dc3-e1db-1f65-0af5d90a61e9.png)