Gremlin Consoleのインストール

https://tinkerpop.apache.org/download.html からZipをDLにして展開、適当な場所に配置する。

インメモリDBで試す

起動方法

bin/gremlin.bat を一般ユーザーで実行する。

         \,,,/
         (o o)
-----oOOo-(3)-oOOo-----
plugin activated: tinkerpop.server
plugin activated: tinkerpop.utilities
plugin activated: tinkerpop.tinkergraph

終了方法

:exit で終了できる。

Tinkerpopの内蔵インメモリグラフDB,TinkerGraphを開く

空のDBを開く場合。

graph = TinkerGraph.open()
g = graph.traversal()

内蔵のサンプルデータ「Modern」を読むならこうする。他にClassicもあるらしい

graph = TinkerFactory.createModern()
g = graph.traversal()
g.V().values("name")

データを追加してみる

// 1. 太郎(person)を追加
taro = g.addV('person').property('name', '太郎').next()

// 2. 花子(person)を追加(友達関係のターゲットとして必要)
hanako = g.addV('person').property('name', '花子').next()

// 3. 太郎から花子へ「friends(友達)」のエッジを追加
g.V(taro).addE('friends').to(hanako).iterate()

// 太郎の友達の名前を検索する
g.V().has('name', '太郎').out('friends').values('name')
==> 花子と出ればOK

// 既存頂点に新しいプロパティを追加する(年齢)
// 太郎に年齢(age)と出身地(city)を追加
g.V().has('person', 'name', '太郎').
      property('age', 30).
      property('city', '東京').iterate()

// 花子に年齢(age)を追加
g.V().has('person', 'name', '花子').
      property('age', 28).iterate()

// 太郎のプロパティをすべて表示
g.V().has('name', '太郎').valueMap()
==> [name:[太郎], age:[30], city:[東京]]

// 30歳以上の人の名前を検索
g.V().has('age', P.gte(30)).values('name')
==> 太郎

// 頂点の追加と複数のプロパティ登録を同時に行う
g.addV('person').property('name', '次郎').property('age', 25).next()

next()やiterate()とは?

next()は、命令を実行し、処理結果の1行目を取得して次の処理に渡す(または画面に表示する)意味。 典型的には、変数に格納して次に進むときに使う。

iterate()は、命令を実行し、画面に結果を表示せずに終端させる意味。

データ設計のうえで、ラベルとプロパティを使い分けるコツは?

この例では、personがラベル、nameやageがプロパティだが、なぜそうするのか?

判断基準の一例は、ラベル=一度決めたら絶対に変わらない値。索引(インデックス)に必ずなるため、 「personラベルの頂点だけの集合」など「検索において、まず最初に仕分けることになる項目」はラベルにするといい。

また、その項目の値ごとに全く違う種類のプロパティの組を持つ場合 (例、personは年齢や居住地を持つ、productは価格や発売年を持つ、等)、その項目はラベルにするといい。

プロパティ=途中で変わる可能性がある値。年齢などがそう。 ラベルを更新する処理は重いため、変わる可能性がある値はラベルにしないのが鉄則らしい。

エッジについても同じ。友達である(friends)という特質は一度決めたら変わらないのでラベルにするが、 いつの友達か(student,mamatomo)やどのくらい深い関係か(rating)などは変わるのでプロパティにするとよい。

マルチラベル

TinkerGraphではラベルは1つの頂点に1つしか付けられないが、Neo4Jなどでは複数付けられる。 つまりpersonでありemployeeでもある頂点を作れる。

プロパティの上書き更新

通常は次のようにすれば、太郎の年齢を30⇒31歳に変更できる。

// 太郎の年齢を30から31に更新する
g.V().has('person', 'name', '太郎').property('age', 31).iterate()
g.V().has('name', '太郎').valueMap()

このとき、同名のプロパティで複数の値を取りうる仕様のDBの場合は、 Cardinarity.Singleを引数に追加する必要があるかも。

プロパティの削除と再追加

g.V().has('person', 'name', '太郎').properties('city').drop().iterate()
g.V().has('person', 'name', '太郎').property('city', '横浜').iterate()

property()とproperties()の使い分けを間違えずに覚えるコツは?

propertyは動詞。プロパティ値の設定や更新に使う。末尾がnext()やiterate()になることが多く、 引数は必ず2つ(キー、値)

propertiesは名詞。プロパティ値の取得に使う。直後にdrop()やvalue()が来ることが多く、 引数は必ず1つ(キーのみ)

なお、次の2行は同じ意味。propertiesで取得した値をただ見るだけなら、valuesを使う方が簡潔。

g.V().properties('age').value()
g.V().values('age')

頂点やエッジの削除

// 太郎から出ている「friends」エッジを全て削除
g.V().has('person', 'name', '太郎').outE('friends').drop().iterate()

// 太郎から出ているfriendsエッジの内、花子とのfriendsエッジのみを削除
// 方法1
g.V().has('person', 'name', '太郎').
      outE('friends').as('e').
      inV().has('name', '花子').
      select('e').drop().iterate()

// 方法2。こちらの方が若干簡潔
g.V().has('person', 'name', '太郎').
      outE('friends').
      where(inV().has('name', '花子')).
      drop().iterate()

// 花子の頂点を削除する(花子に繋がっていたエッジも消えます)
g.V().has('person', 'name', '花子').drop().iterate()

条件検索の方法

数値による比較

// 30歳以上の人を検索 (Greater Than or Equal)
g.V().hasLabel('person').has('age', P.gte(30)).values('name')

// 20歳超、35歳未満の人を検索 (Between)
g.V().hasLabel('person').has('age', P.inside(20, 35)).values('name')

文字列による検索

// 名前に「郎」が含まれる人を検索
g.V().hasLabel('person').has('name', TextP.containing('郎')).values('name')

// 名前の先頭が「花」で始まる人を検索
g.V().hasLabel('person').has('name', TextP.startingWith('花')).values('name')

AND,OR条件

// 「30歳以上」かつ「東京在住」の人を検索 (AND)
g.V().hasLabel('person').has('age', P.gte(30)).has('city', '東京').values('name')

// 「東京在住」または「横浜在住」の人を検索 (OR)
g.V().hasLabel('person').or(has('city', '東京'), has('city', '横浜')).values('name')

エッジの有無など

// 誰かしら「friends(友達)」のエッジを持っている人だけを検索
g.V().hasLabel('person').where(out('friends')).values('name')

// 誰も「friends」のエッジを持っていない(友達がいない)人を検索
g.V().hasLabel('person').not(out('friends')).values('name')

トラバーサル的な(友達の友達)

// まず太郎⇒花子⇒次郎の友達関係を構築
jiro = g.V().has('name', '次郎').next()
g.V(hanako).addE('friends').to(jiro).iterate()

// 各自の友達の名前を検索する
g.V().has('name', '太郎').out('friends').values('name')
g.V().has('name', '花子').out('friends').values('name')

// 太郎の「友達の友達」の名前を取得する
g.V().has('person', 'name', '太郎').
      out('friends'). // 1ステップ目:花子へ
      out('friends'). // 2ステップ目:次郎へ
      values('name')
==> 次郎

// 自分自身(太郎)を除外して、友達の友達を探す
g.V().has('person', 'name', '太郎').as('me').
      out('friends').
      out('friends').
      where(P.neq('me')).
      values('name')

// 太郎から「friends」の関係をちょうど2回繰り返して到達する人を検索
g.V().has('person', 'name', '太郎').
      repeat(out('friends')).times(2).
      values('name')

// 太郎から友達の友達へ至るルートをすべて表示する
g.V().has('person', 'name', '太郎').
      out('friends').
      out('friends').
      path().by('name')
==> [太郎, 花子, 次郎]

順序と最大表示行数

// 年齢が低い順(昇順)に名前を並び替える(デフォルトは昇順)
g.V().hasLabel('person').order().by('age', Order.asc).values('name')

// 年齢が高い順(降順)に名前を並び替える
g.V().hasLabel('person').order().by('age', Order.desc).values('name')

// 全データの中から、最初の3件だけを取得する
g.V().hasLabel('person').values('name').limit(3)

// 年齢が高い人、上位3人の名前と年齢を取得する
g.V().hasLabel('person').
      order().by('age', Order.desc).
      limit(3).
      valueMap('name', 'age')

データのエクスポートとインポート

エクスポート

// 1. GraphML形式で保存(最も汎用的、Gephiなどの外部ツールで開く際におすすめ)
g.io('my-graph.xml').write().iterate()

// 2. GraphSON形式で保存(中身がテキストのJSON形式なので、人間が読みやすい)
g.io('my-graph.json').write().iterate()

:exit

ファイルはGremlin Consoleのホーム(binの一つ上)に作られる。

インポート

// 新しい空のグラフを用意する
graph = TinkerGraph.open()
g = graph.traversal()

// 以前保存したファイルを読み込む
g.io('my-graph.xml').read().iterate()

// 読み込まれたか確認
g.V().valueMap()