GraphDBを学ぶ（３）neo4j：ノードとエッジの作成

# TL;DR

* neo4jを使ってGraphDBの扱い方を学びます
    * その第1段として，手作業でデータを登録し，検索する方法を紹介します
* RDBMSベンチマーク[TPC-H](http://www.tpc.org/tpch/)のテーブルを参考に，neo4jにノードを登録してみます（nation, regionのみ）

# 作業方針

* [データベースベンチマークTPC-HをMySQLで実行する(TPC-H v2.18.0)](https://qiita.com/myoshimi/items/bd460164af48ece1ab2d)を参考に， **sf=0.01** のサイズでデータベースを作成してデータの確認に使用します
    * sf=1でやったらデータの登録が終わらなるかもしれいないので，この記事では小さい(sf=0.01)データを扱います
* TPC-Hのテーブルを見ながら，neo4jにデータを（最初は手作業で）投入，検索をしてみます
* CUIのneo4jクライアント[neo4j-client](https://neo4j-client.net/)を使用します

# シンプルなデータ登録(Nation, Regionテーブルを参考に)

* まず，投入するデータについて考えます
    * TPC-Hのテーブル群の中で，Nationテーブル(25エントリ)，Regionテーブル(5エントリ)は，has-manyの関係にあり，scale factorにかかわらず各テーブルのエントリ数が固定です．
        * 使いやすそうな関係なので，neo4j(Cypher)のテストに使います
    * regionテーブルのエントリをグラフのノードへ登録する際には，Webクライアントで実験します
    * nationテーブルは，[neo4j-client](https://neo4j-client.net/)を使います

![nr.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309617/4a603c84-34ce-a1c6-7664-aac768978cd9.jpeg)

## regionノードの登録

* まず，MySQLで登録されているRegionテーブルの中身を確認します
    * MariaDBなどのMySQLサーバにTPC-Hのテーブルを登録する方法は，[データベースベンチマークTPC-HをMySQLで実行する(TPC-H v2.18.0)](https://qiita.com/myoshimi/items/bd460164af48ece1ab2d)を参照してください
* regionテーブルに登録されたエントリを確認します

```bash
MariaDB [sf1]> select * from region;
+-------------+-------------+---------------------------------------------------------------------------------------------------------------------+
| R_REGIONKEY | R_NAME      | R_COMMENT                                                                                                           |
+-------------+-------------+---------------------------------------------------------------------------------------------------------------------+
|           0 | AFRICA      | lar deposits. blithely final packages cajole. regular waters are final requests. regular accounts are according to  |
.....<snip>.....
|           4 | MIDDLE EAST | uickly special accounts cajole carefully blithely close requests. carefully final asymptotes haggle furiousl        |
+-------------+-------------+---------------------------------------------------------------------------------------------------------------------+
5 rows in set (0.00 sec)
```

* 5つのRegion(地域)が登録されています
* Regionテーブルは，```R_NAME``` (fixed text, size 25)および```R_COMMENT``` (variable text, size 152)という，2つの属性を持つエントリからなります
* そこで，neo4jに，各エントリをノードとして登録してみます

### Cypherクエリでノードを1つ登録

* R_NAMEが"AFRICA"のregionノードを登録してみます

```bash
CREATE
(:region{R_REGIONKEY:0, R_NAME:"AFRICA",R_COMMENT:"lar deposits. blithely final packages cajole. regular waters are final requests. regular accounts are according to"});
```

* WebUIのコンソールに入力する場合は，以下のようにコマンドを記述してエンターキーか，再生ボタンを押します

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309617/e4f6fd8b-5860-caee-a953-807875b40746.jpeg" size=50%>

* (おまけ)ノードを削除したい場合は，該当するノードを```MATCH```句で取り出して```DELETE```します

```bash
MATCH (r :region{R_REGIONKEY:0}) DELETE r;
```

* 以下のようなログ出力が行われ，ノードの登録ができたログが出力されます

```
Added 1 label, created 1 node, set 2 properties, completed after 12 ms.
```

### Cypherクエリで複数ノードをまとめて登録

* ```create```句に加えて，ノードの情報を列挙すると，複数のノードをまとめて登録することができます．この操作で，残りのノードを登録します．

```bash
create
(:region{R_REGIONKEY:1, R_NAME:"AMERICA",R_COMMENT:"hs use ironic, even requests. s"}),
(:region{R_REGIONKEY:2, R_NAME:"ASIA",R_COMMENT:"ges. thinly even pinto beans ca"}),
(:region{R_REGIONKEY:3, R_NAME:"EUROPE",R_COMMENT:"ly final courts cajole furiously final excuse"}),
(:region{R_REGIONKEY:4, R_NAME:"MIDDLE EAST",R_COMMENT:"uickly special accounts cajole carefully blithely close requests. carefully final asymptotes haggle furiousl"});
```

* WebUIでは以下のように入力されることになります

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309617/c4acd3af-0b84-efb5-8f01-17bafe2e3ae9.jpeg" size=50%>

### 登録ノードの確認

* ```match```句ですべてのノード（条件無しでマッチ）を返す（表示する）クエリを発行します

```bash
match(n) return n
```

* WebUIの場合，以下のようなグラーフとして表示されます
    * マウスでノードをドラッグできるので楽しいです
    * neo4j-clientでは行が表示されます

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309617/8978ccf0-4f73-71f6-3754-78e56c0b66ed.jpeg" size=50%>

## nationノードの登録

* 前項のregionと同様の方法で，nationもノード登録します
* しかし，プロンプトで登録していくのは面倒なので，csvファイルから取り込みます

### MySQLからテーブルをcsv出力する

* 1ノードずつ登録するのは面倒なので，まとめて登録する方法を考えます
* mysqlのコマンドで，nationテーブルをcsvに出力します(参考：[MySQLのSELECT文でcsvを出力する](https://qiita.com/catatsuy/items/9fdf4423d5f4885b9bf9))

```sql
select N_NATIONKEY, N_NAME, N_COMMENT from nation
    into outfile '/tmp/nation.csv'
    fields terminated by ','
    optionally enclosed by '"';
```

* ```/tmp/nation.csv```に，以下のような国名とコメントのリストが得られます

```bash
cat /tmp/nation.csv

"ALGERIA"," haggle. carefully final deposits detect slyly agai"
.....<snip>.....
"UNITED STATES","y final packages. slow foxes cajole quickly. quickly silent platelets breach ironic accounts. unusual pinto be"
```

### csvファイルのノードをまとめてneo4jに登録する

* インポートしたいファイルをインポート用ディレクトリ（デフォルトでは```/var/lib/neo4j/import```）に置いて，```load csv```コマンドで，取り込むことができます．
    * csvファイルを1行ずつ読み込み，カンマでsplitして配列(line)に分解します
    * 配列の対応する要素（line[0]など）を，ノードの属性に設定して ```CREATE``` します

```bash
neo4j-client -u neo4j localhost

neo4j>
LOAD CSV FROM 'file:///nation.csv' AS line
CREATE (:nation {N_NATIONKEY: toInteger(line[0]), N_NAME: line[1], N_COMMENT: line[2]});

Created 25 nodes, set 75 properties, added 25 labels
0 rows returned in 106ms (rendered after 126ms)
```

* 25個のノードが作られた旨のメッセージが出れば成功です
* WebUIで確認すると，regionとnationの2種類のラベルでノードが合計30個できていることを確認できます

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309617/a2dd8adf-ccbd-4039-5bc9-942f6991e89a.jpeg" size=50%>

# ノード間の関係の登録

* regionとnationの関係（nationはregionにある）を登録します

## Cypherクエリで関係を登録する

* nationの各ノードは，いずれかのregionが設定されます
    * RDBMSの文脈では，nationテーブルの```N_REGIONKEY```に対応する```R_REGIONKEY```の値を持つregionのエントリがあります
    * その国(nation)はあの地域(region)に「ある」という関係を```AT```という名前で示すことにします
* ノード間の関係は，以下のようなCypherクエリで設定できます
    * 以下の例では，```N_NATIONKEY=0```のnationノード(アルジェリア)が```R_REGIONKEY=0```のregionノード(アフリカ)にある，という関係を設定しています

```bash
neo4j>
MATCH(n:nation{N_NATIONKEY: 0}), (r:region{R_REGIONKEY: 0})
CREATE (n)-[:AT]->(r);
```

* WebUIから，関係の接続関係を見ることができます

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309617/f34fc355-9636-8927-eb19-34119a0d25d5.jpeg" size=50%>

* (おまけ)関係の削除は，以下のように，特定の関係に名前（```b```）を付けて，```DELETE```を実行します

```bash
neo4j>
MATCH (n:nation{N_NATIONKEY: 0}) -[b:AT]-> 
(r:region{R_REGIONKEY: 0}) DELETE b;
```

## 関係をnationのcsvファイルから設定する

* コマンド入力は面倒なので，各nation.csvの外部キー情報を使って，どのregionに属しているかの関係を設定します

```sql
neo4j>
LOAD CSV FROM 'file:///nation.csv' AS line
  MATCH (n:nation{N_NATIONKEY: toInteger(line[0])})
  MATCH (r:region{R_REGIONKEY: toInteger(line[2])})
CREATE (n)-[:AT]->(r);

Created 25 relationships);
0 rows returned in 83ms (rendered after 89ms)
```

* WebUIから，国と地域がそれぞれノードとして存在し，国が地域に所属している関係をみることができます

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309617/78c5c0ed-8832-ff5b-b82c-bf41b680b23f.jpeg" size=50%>

# 参考

* [TPC-H](http://www.tpc.org/tpch/)
* [MySQLのSELECT文でcsvを出力する](https://qiita.com/catatsuy/items/9fdf4423d5f4885b9bf9)
* [データベースベンチマークTPC-HをMySQLで実行する(TPC-H v2.18.0)](https://qiita.com/myoshimi/items/bd460164af48ece1ab2d)
* [neo4j document 3.20. LOAD CSV](https://neo4j.com/docs/cypher-manual/current/clauses/load-csv/)