neo4j 実例で学ぶCypher -2-

# はじめに前提的なこと
* 楽天のような店子をたくさん持つプラットフォーマーの立場で考えます
* [前回](http://qiita.com/nakamura-tsuyoshi/items/4a0cbf20ce9ba4c934b9)、[前々回](http://qiita.com/nakamura-tsuyoshi/items/d284ad1eb7ca848d5d79)から引き続きです


# サンプルデータの用意
## データ構造
![ccc.png](https://qiita-image-store.s3.amazonaws.com/0/106937/e7252d07-0509-835e-68b3-2ffd07330d64.png)



## CSVにすると

```category.csv
category_id:ID,name,:LABEL
cate1,カテゴリ1,category
```

```shops.csv
shop_id:ID,name,category_id,:LABEL
shop1,ショップA,cate1,shop
shop2,ショップB,cate1,shop
```

```items.csv
tem_id:ID,name,shop_id,:LABEL
item1,商品A,shop1,item
item2,商品B,shop1,item
item3,商品C,shop2,item
item4,商品D,shop2,item
item5,商品E,shop2,item
```

```orders.csv
order_id:ID,date,user_id,shop_id,category_id,price,item_id,:LABEL
order1,2016-04-02,user1,shop1,cate1,9500,item1,order
order2,2016-04-03,user2,shop1,cate1,1000,item1,order
order3,2016-04-04,user3,shop1,cate1,1200,item2,order
order4,2016-04-05,user4,shop2,cate1,15000,item3,order
```

```users.csv
user_id:ID,name,email,age:int,industry,gender,:LABEL
user1,user_name1,1@example.com,37,サービス業,male,user
user2,user_name2,2@example.com,17,ネットサービス,male,user
user3,user_name3,3@example.com,27,公務員,female,user
user4,user_name4,4@example.com,44,公務員,male,user
user5,user_name5,4@example.com,55,公務員,male,user
```

```owner.csv
owner_id:ID,name,:LABEL
owner1,owner1,owner
owner2,owner2,owner
```


## サンプルデータの投入
```
➜  neo4j neo4j-import --into /usr/local/Cellar/neo4j/2.3.2/libexec/data/graph9.db --nodes category.csv --nodes items.csv --nodes orders.csv --nodes shops.csv --nodes users.csv --nodes owner.csv
Unable to find any JVMs matching version "1.7".
Importing the contents of these files into /usr/local/Cellar/neo4j/2.3.2/libexec/data/graph9.db:
Nodes:
  /Users/a12091/neo4j/category.csv

  /Users/a12091/neo4j/items.csv

  /Users/a12091/neo4j/orders.csv

  /Users/a12091/neo4j/shops.csv

  /Users/a12091/neo4j/users.csv

  /Users/a12091/neo4j/owner.csv

Available memory:
  Free machine memory: 18.05 MB
  Max heap memory : 1.78 GB

Nodes
[*>:??-------------------------------------------------------------------------------------|P||] 10k
Done in 594ms
Prepare node index
[*DETECT:7.63 MB-------------------------------------------------------------------------------]   0
Done in 24ms
Calculate dense nodes
[*>:??-----------------------------------------------------------------------------------------]   0
Done in 12ms
Relationships
[*>:??-----------------------------------------------------------------------------------------]   0
Done in 10ms
Node --> Relationship
[*>:??------------------------------------------|LINK-------------------|v:??------------------] 10k
Done in 11ms
Relationship --> Relationship
[*>:??-----------------------------------------------------------------------------------------]   0
Done in 12ms
Node counts
[*COUNT:76.29 MB-------------------------------------------------------------------------------] 10k
Done in 133ms
Relationship counts
[*>:??-----------------------------------------------------------------------------------------]   0
Done in 1ms

IMPORT DONE in 2s 345ms. Imported:
  19 nodes
  0 relationships
  85 properties
```

## リレーションをはる
### buy
* 注文とuserを紐付ける

```
MATCH (o:order),(u:user) 
WHERE o.user_id = u.user_id 
CREATE (u)-[:BUY]->(o);
```

### order
* 注文データと商品を紐付ける

```
MATCH (o:order),(i:item) 
WHERE o.item_id = i.item_id 
CREATE (o)-[:ORDER]->(i);
```

### part of
* 商品とショップを紐付ける

```
MATCH (s:shop),(i:item)
WHERE s.shop_id = i.shop_id
CREATE (i)-[:PART]->(s);
```

### categorize
* ショップとカテゴリーを紐付ける

```
MATCH (c:category),(s:shop)
WHERE c.category_id = s.category_id
CREATE (s)-[:CATE]->(c);
```

### frend
* ショップオーナーと友人ユーザを紐付ける

```
MATCH (ow:owner),(u:user)
WHERE ow.owner_id = "owner1" AND u.user_id = "user1"
CREATE (ow)-[:FREND]->(u);

MATCH (ow:owner),(u:user)
WHERE ow.owner_id = "owner1" AND u.user_id = "user2"
CREATE (ow)-[:FREND]->(u);
```

### recommend
* ショップオーナーのおすすめ商品を紐付け

```
MATCH (ow:owner),(i:item)
WHERE ow.owner_id = "owner1" AND i.item_id = "item1"
CREATE (ow)-[:RECCOMEND]->(i);
```

### favorite
* ユーザとお気に入り商品を紐付ける

```
MATCH (u:user),(i:item)
WHERE u.user_id = "user3" AND i.item_id = "item2"
CREATE (u)-[:FAVORITE]->(i);

MATCH (u:user),(i:item)
WHERE u.user_id = "user3" AND i.item_id = "item3"
CREATE (u)-[:FAVORITE]->(i);

MATCH (u:user),(i:item)
WHERE u.user_id = "user4" AND i.item_id = "item2"
CREATE (u)-[:FAVORITE]->(i);

MATCH (u:user),(i:item)
WHERE u.user_id = "user4" AND i.item_id = "item4"
CREATE (u)-[:FAVORITE]->(i);

MATCH (u:user),(i:item)
WHERE u.user_id = "user5" AND i.item_id = "item5"
CREATE (u)-[:FAVORITE]->(i);
```

### owner
* ショップオーナーとショップを紐付ける

```
MATCH (ow:owner),(s:shop)
WHERE ow.owner_id = "owner1" AND s.shop_id = "shop1"
CREATE (ow)-[:OWMER]->(s);


MATCH (ow:owner),(s:shop)
WHERE ow.owner_id = "owner2" AND s.shop_id = "shop2"
CREATE (ow)-[:OWMER]->(s);

```

# 抽出したいデータ（ここからがメイン）
## 1:内訳としてショップAの購入者でオーナーの知人とそうでない人はどのくらいのか
### サンプルデータから自分でデータを追うと
* user1とuser2の２名が知人でuser3は知人ではない人からの購入
* `:FREND`というリレーションが貼られてるのはuser1とuser3のみ
* なので知人が２でその他は1となる

### Cypherで書いてみる
```
neo4j-sh (?)$ MATCH (u:user)-->(o:order)-->(i:item)-->(shop {name:"ショップA"})<--(ow:owner)-[:FREND]->(user)
> RETURN count(DISTINCT user) as frend_of_owner , count(DISTINCT u) - count(DISTINCT user) as other
> ;
+------------------------+
| frend_of_owner | other |
+------------------------+
| 2              | 1     |
+------------------------+
```
* 知人が２でその他は1となり一致

## 2:favorite（お気に入り）が多い商品は
### サンプルデータから表にまとめてみると
|user|favorite item|
|:---|:---:|
|user3|商品B|
|user3|商品C|
|user4|商品B|
|user4|商品D|
|user5|商品E|

* 商品Bがuser3とuser4からお気に入り登録されているので2ユーザがfavorite
* 商品Cがuser3からお気に入り登録されているので1ユーザがfavorite
* 商品Dがuser4からお気に入り登録されているので1ユーザがfavorite
* 商品Eがuser5からお気に入り登録されているので1ユーザがfavorite

が答え

### Cypherで書いてみる
```
neo4j-sh (?)$ MATCH (u:user)-[:FAVORITE]->(i:item)
> RETURN i.name,count(*)
> ;
+-------------------+
| i.name | count(*) |
+-------------------+
| "商品E"  | 1        |
| "商品B"  | 2        |
| "商品D"  | 1        |
| "商品C"  | 1        |
+-------------------+
4 rows
37 ms
```
* `count(*)`はユーザ数を表す
* それぞれ一致

## 3:商品をfavorite(お気に入り)をして該当の商品を購入したユーザ数は？
### サンプルデータから表にまとめてみると
|商品|favorite|購入したか|購入user|
|:---|:---:|:---:|:---:|
|商品A|x|○|user1|
|商品A|x|○|user2|
|商品B|○|○|user3|
|商品B|○|x|user4|
|商品C|○|x|user3|
|商品C|x|○|user4|
|商品D|○|x|user4|
|商品E|○|x|user5|

* `favorite=○`で且つ`購入したか=○`を探す
* user３だけが商品Bをfavoriteして購入しているので1ユーザが答え

### ビジュアライズしてみる
![Neo4j-111.png](https://qiita-image-store.s3.amazonaws.com/0/106937/dc3802e9-2e84-788c-784a-d2a3424adef7.png)


### Cypherで書いてみる パターン1
```
neo4j-sh (?)$ MATCH (u:user)-[:FAVORITE]->(i:item)<--(o:order)<--(u:user)
> RETURN count(*);
+----------+
| count(*) |
+----------+
| 1        |
+----------+
```

### Cypherで書いてみる パターン2
```
neo4j-sh (?)$ MATCH (u:user)-[:FAVORITE]->(i:item),(u:user)-->(o:order)-->(i:item)
> RETURN COUNT(*);
+----------+
| COUNT(*) |
+----------+
| 1        |
+----------+
1 row
82 ms
```

### Cypherで書いてみる パターン3
```
neo4j-sh (?)$ MATCH (u:user)-->(o:order)-->(i:item)
> WHERE u-[:FAVORITE]->i
> RETURN count(*);
+----------+
| count(*) |
+----------+
| 1        |
+----------+
1 row
109 ms
```
* `count(*)`はユーザ数を表す
* 1ユーザとなり一致。色々と書き方がある



## 4:商品をfavoite(お気に入り)をしてないけど購入したユーザ数は？
### サンプルデータから表にまとめてみると
|商品|favorite|購入したか|購入user|
|:---|:---:|:---:|:---:|
|商品A|x|○|user1|
|商品A|x|○|user2|
|商品B|○|○|user3|
|商品B|○|x|user4|
|商品C|○|x|user3|
|商品C|x|○|user4|
|商品D|○|x|user4|
|商品E|○|x|user5|

* `購入したか=○`で且つ`favorite=x`を探す
* ユーザはuser1,user2,user4はfavoriteしていなが購入しているので3ユーザが答え

### ビジュアライズしてみる
![Neo4j.png](https://qiita-image-store.s3.amazonaws.com/0/106937/540a5501-6b14-cd29-e2f1-1bc998b2d714.png)


### Cypherで書いてみる
```
neo4j-sh (?)$ MATCH (u:user)-->(o:order)-->(i:item)
> WHERE NOT u-[:FAVORITE]->i
> RETURN count(*)
> ;
+----------+
| count(*) |
+----------+
| 3        |
+----------+
1 row
81 ms
```
* `count(*)`はユーザ数を表す
* 3ユーザとなり一致



## 5:商品をfavorite（お気に入り）をしたが該当の商品を買わなかったユーザ数は？
### サンプルデータから表にまとめてみると
|商品|favorite|購入したか|購入user|
|:---|:---:|:---:|:---:|
|商品A|x|○|user1|
|商品A|x|○|user2|
|商品B|○|○|user3|
|商品B|○|x|user4|
|商品C|○|x|user3|
|商品C|x|○|user4|
|商品D|○|x|user4|
|商品E|○|x|user5|

* `favorite=○`で且つ`購入したか=x`を探す
* favorite(お気に入り)をしたが、購入していないユニークユーザはuser3,user4,user5で3ユーザが答え

### ビジュアライズしてみる
![Neo4j-1.png](https://qiita-image-store.s3.amazonaws.com/0/106937/80dd7565-d17d-cd6b-b36e-c4a167cf9f4e.png)

* 注意）商品Bはuser3からfavoriteされ、購入しているが、user4からfavoriteされて購入されていない

### Cypherで書いてみる
```
neo4j-sh (?)$ MATCH (u:user)-[:FAVORITE]->(i:item),(o:order)
> WHERE NOT o<--u
> RETURN count(DISTINCT u);
+-------------------+
| count(DISTINCT u) |
+-------------------+
| 3                 |
+-------------------+
1 row
89 ms
```
* `count(*)`はユーザ数を表す
* 3ユーザとなり一致

# まとめ、雑感的なもの
* ちょっと複雑めにリレーションを張って色々な条件で抽出してみたが、まぁなんとなくできた
* Webインターフェースがあったおかげで理解がスムーズだった
 * チュートリアルもわかりやすかった
* グラフDBを使うことで割と欲しい集計データが簡単に抽出できるとわかった
* プロダクジョンではmysqlを使ってて、どういう方法、タイミングでneo4jへ連携する部分でまだ考えなきゃいけないなぁと感じた
 * 夜間バッチかなぁ。。。毎晩夜間バッチでslaveのmysqlのデータをneo4jへ突っ込む的な
* Cypherでサブクエリ的なものが使えないっぽいのでリレーションが重要だと感じた
* Cypherには`EXPLAIN`,`PROFILE`があるので自分のQLの計測が出来る。プロダクジョンとかの大量のデータを扱う際に必要になってくるだろうな
* スケールに関するものも一通りあるっぽいなぁ　http://neo4j.com/docs/stable/ha.html
* 可能性はいろいろあるね
 * ただ負荷に対するノウハウ等が少ないのでいきなりユーザが使うサービスには使うのは怖いかな
 * 社内的な分析にまずは使ってみるが良いんじゃないかなと