この記事は
グラフDBをいじってみたくなったので、Neo4jを触ろうとした際の備忘録である。
レファレンスを読んでみた感じ面白そう。特に独自のQuery LanguageであるCypherはとても興味深い。
公式に載ってることしか書いてないので、そっちを読みましょう。
https://neo4j.com/docs/developer-manual/current/
必要となる概念
グラフDBはグラフ理論に基づいたデータ構造を扱うためのデータベースである。
そのため取り扱われる概念も基本的にグラフ理論によって導入されたものがほとんどである。
いくつかの概念はRDBにて用いられているものに呼び変えられているが、RDBを知らなくても(そんな人がグラフDBを扱おうとは思わないだろうが)十分理解ができる。
- Node
- Entityのこと。LabelとPropertyを持つ
- Relationship
- Node間(自己参照も含む)を結ぶ線のこと
- Label
- Entityの名前
- Property
- Entityの属性
- 取りうる値
- Numeric(integer/float)
- Boolean
- String
- List(上記三種の)
- Null(Property値ではなく、Propertyに値が入っていない状態を指す)
- Paths
- Nodeと(optionalで)Relationの単位
- Relationの数がPathのLengthとなる。
- 自己参照型のNodeであればLength=1
- Node単体であればLength=0
CypherのSummary
Neo4j独自のQuery Language。影響を受けている言語は下記とのこと。
- where(制約)とorderby(ソート)はSQLから
- regex(パターンマッチ)はSPARQLから
- list周りのsemanticsはHaskel/Pythonから
基本的なSyntaxは下記。
-
Node
- (hoge)
-
Relationship
- (a)--(b)
- 無向性
- (a)->(b)
- 有向性
- (a)-[rel]-(b)
- 特定のrelationshipを指定する場合
- (a)-[*..5]-(b)
- a/b間でlengthが5以下のpathsを返す
- (a)--(b)
-
Match
- DB内の起点となるNode群を指定する
- Where句やReturn句で使いたいNode群にはこのタイミングで参照を取る必要がある
-
With
- Match句で取得した情報をどのように取得したいかを指定する
-
Where
- Match句で取得したNode群に絞込を掛ける
-
Return
- 実際にDBから取得したいProperty
-
Create[Delete]
- Node/Relationを作成削除する
-
Set[Remove]
- Label/Propertyを作成更新削除する
-
Merge
- Node/PatternをRDBで言うところのUpsertする
- PatternはMatch句で取得したNode群を指す
- Node/PatternをRDBで言うところのUpsertする
// Johnの友達の友達(friend of friend)の一覧を取得する// MATCH句の()内にLabelとその制約を与えるイメージ
MATCH (john {name: 'John'})-[:friend]->()-[:friend]->(fof)
RETURN john.name, fof.name
// 指定のnameを持つNodeがfollowしている、nameが'S'から始まるNodeの一覧を取得する
MATCH (user)-[:friend]->(follower)
WHERE user.name IN['Joe', 'John', 'Sara', 'Maria', 'Steve'] AND follower.name =~ 'S.*'
RETURN user.name, follower.name
// 友達が4人以上いるJohnを返す
MATCH (n {name: 'John'})-[:frined]-(friend)
WITH n, count(friend) AS friendsCount
WHERE friendsCount > 3
RETURN n, friendsCount
// 友達の数をJohnのfriendsCount propertyにSETし、それを返す
MATCH (n {name: 'John'})-[:friend]-(friend)
WITH n, count(friend) AS friendsCount
SET n.friendsCount = friendsCount
RETURN n.friendsCount
// User Entityを3つ作成、それらをfriend relationshipで結ぶ
CREATE (adam:User {name: 'Adam'}), (pernilla:User {name: 'Pernilla'}), (david:User {name: 'David')},
(adam)-[:friend]->(perniilla), (pernilla)-[:friend]->(david)
// User:Adamの友達の友達(David)を返す
MATCH (user:User {name: 'Adam'})-[r1:friend]-()-[r2:friend]-(fof)
RETURN fof.name AS fofName
// Case
MATCH (n)
RETURN
CASE n.eyes
WHEN 'blue' THEN 1
WHEN 'brown' THEN 2
ELSE 3 END AS result
Parameterを利用したQuery
// parameter
{
"name" : "Johan"
}
// query
MATCH (n:Person)
WHERE n.name = $name
RETURN n
// parameter
{
"regex" : ".*h.*"
}
// query
MATCH (n:Person)
WHERE n.name =~ $regex
RETURN n.name
// parameter
{
"props" : {
"name" : "Andres",
"position" : "Developer"
}
}
// query
CREATE ($props)
List型
RETURN [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
//=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
RETURN range(0, 10)[3]
//=> 3
RETURN range(0, 10)[0..3]
//=> [0, 1, 2]
Map型
RETURN { key: 'Value', listKey: [{ inner: 'Map1' }, { inner: 'Map2' }]}
//=> {listKey -> [{inner -> "Map1"},{inner -> "Map2"}], key -> "Value"}
MATCH (actor:Person)-[:ACTED_IN]->(movie:Movie)
WITH actor, count(movie) AS nrOfMovies
RETURN actor { .name, nrOfMovies }
//=> {name -> "Martin Sheen", nrOfMovies -> 2}
Match Statement
Aggregate元の指定。SQLではFromに該当する。
// Oliver Stoneと隣接する全てのmovie
MATCH (director { name: 'Oliver Stone' })--(movie)
RETURN movie.title
// Person labelのOliver Stoneと隣接する全てのMovie labelを持つnodeを返す
MATCH (:Person { name: 'Oliver Stone' })--(movie:Movie)
RETURN movie.title
// Movie LabelのWall Streetにacted_inなnodeを返す
MATCH (wallstreet:Movie { title: 'Wall Street' })<-[:acted_in]-(actor)
RETURN actor.name
// wallstreetにacted_inまたはdirectedなnodeを返す
MATCH (wallstreet { title: 'Wall Street' })<-[:acted_in|:directed]-(person)
RETURN person.name
// 特定のProperty値を持つ経路を返す
MATCH p =(charlie:Person)-[* { blocked:false }]-(martin:Person)
WHERE charlie.name = 'Charlie Sheen' AND martin.name = 'Martin Sheen'
RETURN p
//=> [Node[0]{name:"Charlie Sheen"},:X[20]{blocked:false},Node[20]{},:X[21]{blocked:false},Node[1]{name:"Martin Sheen"}]
// MartinとOliverとの最短経路を返す
MATCH (martin:Person { name: 'Martin Sheen' }),(oliver:Person { name: 'Oliver Stone' }), p = shortestPath((martin)-[*..15]-(oliver))
RETURN p
//=> [Node[1]{name:"Martin Sheen"},:ACTED_IN[1]{role:"Carl Fox"},Node[5]{title:"Wall Street"},:DIRECTED[3]{},Node[3]{name:"Oliver Stone"}]
Optional Match Statement
Matchと異なり、一致するパターンが見つからなかった時にnullを返す。
SQLで言うところの、Outer JoinのようなStatement。
MATCH (a:Movie { title: 'Wall Street' })
OPTIONAL MATCH (a)-->(x)
RETURN x
Return Statement
実際に返す値を指定する。SQLで言うところのSelect。
MATCH (n { name: 'A' })-[r:KNOWS]->(c)
RETURN r
With Statement
MatchでAggregateしたものに集約関数等を用いる際には、With Statementを用いる。
MATCH (david { name: 'David' })--(otherPerson)-->()
WITH otherPerson, count(*) AS foaf
WHERE foaf > 1
RETURN otherPerson.name
MATCH (n)
WITH n
ORDER BY n.name DESC LIMIT 3
RETURN collect(n.name)
Unwind Statement
Listをrowとして変形する。
UNWIND [1, 2, 3] AS x
RETURN x
// Parameter
{
"events" : [ {
"year" : 2014,
"id" : 1
}, {
"year" : 2014,
"id" : 2
} ]
}
// Query
UNWIND $events AS event
MERGE (y:Year { year: event.year })
MERGE (y)<-[:IN]-(e:Event { id: event.id })
RETURN e.id AS x
ORDER BY x
Where Statement
SQLでのWhereと同様の役割を果たす。
AND/OR/XOR/NOT演算子が使える。
MATCH (n)
WHERE n.name = 'Peter' XOR (n.age < 30 AND n.name = 'Tobias') OR NOT (n.name = 'Tobias' OR n.name = 'Peter')
RETURN n.name, n.age
Label型によるfileter
MATCH (n)
WHERE n:Swedish
RETURN n.name, n.age
Propertyの存在有無でfilter
MATCH (n)
WHERE exists(n.belt)
RETURN n.name, n.belt
string matchによるfilter
MATCH (n)
WHERE n.name STARTS WITH 'Pet'
RETURN n.name, n.age
正規表現matchによるfilter
MATCH (n)
WHERE n.name =~ 'Tob.*'
RETURN n.name, n.age
path patternによるfilter
MATCH (tobias { name: 'Tobias' }),(others)
WHERE others.name IN ['Andres', 'Peter'] AND (tobias)<--(others)
RETURN others.name, others.age
Order By Statement
MATCH (n)
RETURN n.name, n.age
ORDER BY n.age, n.name
Create Statement
Nodeの作成
// Label = Person, Property = name/title
CREATE (n:Person { name: 'Andres', title: 'Developer' })
Relationshipの作成
// :RELTYPEというRelationshipをa/b間に作成する
MATCH (a:Person),(b:Person)
WHERE a.name = 'Node A' AND b.name = 'Node B'
CREATE (a)-[r:RELTYPE]->(b)
RETURN r
Pathの作成
CREATE p =(andres { name:'Andres' })-[:WORKS_AT]->(neo)<-[:WORKS_AT]-(michael { name: 'Michael' })
RETURN p
Parameterによる作成
// Parameter
{
"props" : [ {
"name" : "Andres",
"position" : "Developer"
}, {
"name" : "Michael",
"position" : "Developer"
} ]
}
// Query
UNWIND $props AS map
CREATE (n)
SET n = map
Delete Statement
Nodeの削除
MATCH (n { name: 'Andres' })
DETACH DELETE n
Relationshipの削除
MATCH (n { name: 'Andres' })-[r:KNOWS]->()
DELETE r
Set Statement
Set Property
MATCH (n { name: 'Andres' })
SET n.surname = 'Taylor'
RETURN n
Remove Property
MATCH (n { name: 'Andres' })
SET n.name = NULL RETURN n
Parameterを用いてPropertyを追加
MATCH (peter { name: 'Peter' })
SET peter += { hungry: TRUE , position: 'Entrepreneur' }
Parameterを用いてPropertyを更新
// Parameters
{
"props" : {
"name" : "Andres",
"position" : "Developer"
}
}
// Query
MATCH (n { name: 'Andres' })
SET n = $props
RETURN n
LabelをSet
MATCH (n { name: 'Stefan' })
SET n:German
RETURN n
Remove Statement
Nodeを削除
MATCH (andres { name: 'Andres' })
REMOVE andres.age
RETURN andres
Labelを削除
MATCH (n { name: 'Peter' })
REMOVE n:German
RETURN n
Foreach Statement
List型を更新する際などに用いる。
MATCH p =(begin)-[*]->(END)
WHERE begin.name = 'A' AND END.name = 'D'
FOREACH (n IN nodes(p)| SET n.marked = TRUE )
Merge Statement
与えた引数をUpsertするイメージ
LabelのMerge
MERGE (robert:Critic)
RETURN robert, labels(robert)
NodeのMerge
MERGE (charlie { name: 'Charlie Sheen', age: 10 })
RETURN charlie
ON CREATEによるフック
MERGE (keanu:Person { name: 'Keanu Reeves' })
ON CREATE SET keanu.created = timestamp()
RETURN keanu.name, keanu.created
ON MATCHによるフック
MERGE (person:Person)
ON MATCH SET person.found = TRUE
RETURN person.name, person.found
Union Statement
Union Allは重複を許す。
MATCH (n:Actor)
RETURN n.name AS name
UNION ALL
MATCH (n:Movie)
RETURN n.title AS name
Unionは重複を許さない。
MATCH (n:Actor)
RETURN n.name AS name
UNION
MATCH (n:Movie)
RETURN n.title AS name
CSV
"1","ABBA","1992"
"2","Roxette","1986"
"3","Europe","1979"
"4","The Cardigans","1992"
LOAD CSV FROM 'https://neo4j.com/docs/developer-manual/3.3/csv/artists.csv' AS line
CREATE (:Artist { name: line[1], year: toInteger(line[2])})
"Id","Name","Year"
"1","ABBA","1992"
"2","Roxette","1986"
"3","Europe","1979"
"4","The Cardigans","1992"
LOAD CSV WITH HEADERS FROM 'https://neo4j.com/docs/developer-manual/3.3/csv/artists-with-headers.csv' AS line
CREATE (:Artist { name: line.Name, year: toInteger(line.Year)})
Index
single-property index
CREATE INDEX ON :Person(firstname)
composite-property index
CREATE INDEX ON :Person(firstname, surname)
Constraints
CREATE CONSTRAINT ON (book:Book) ASSERT book.isbn IS UNIQUE
DROP CONSTRAINT ON (book:Book) ASSERT book.isbn IS UNIQUE
Driver
JSのクライアントが公式で用意されている。
npm install neo4j-driver
実行計画やクエリチューニングに関しても記載があったが、今回はスキップということで。
とりあえずサンプルアプリを作ってみて感想を別途書きたい所存。