Neo4j をDockerで立ち上げてグラフDBを触ってみる
はじめに
グラフデータベース Neo4j を Docker でローカルに立ち上げ、基本的なデータ操作を試してみます。
Neo4j とは
Neo4j はグラフデータベースです。データを**ノード(節点)とリレーションシップ(辺)**で表現します。
(Alice)-[:KNOWS]->(Bob)-[:WORKS_AT]->(Acme Corp)
↑ノード ↑リレーションシップ ↑ノード
| 概念 | 説明 |
|---|---|
| ノード | エンティティ(人、会社、スキルなど) |
| リレーションシップ | ノード間の関係(向きがある) |
| プロパティ | ノード/リレーションシップのキーバリューデータ |
| ラベル | ノードの種類(:Person, :Company など) |
クエリ言語は Cypher を使います。SQL に相当する独自言語で、グラフのパターンを視覚的に表現できます。
環境構築
docker-compose.yml を作成
services:
neo4j:
image: neo4j:latest
container_name: neo4j
ports:
- "7474:7474" # Browser UI
- "7687:7687" # Bolt protocol
environment:
- NEO4J_AUTH=neo4j/password123
- NEO4J_PLUGINS=["graph-data-science"]
volumes:
- neo4j_data:/data
- neo4j_logs:/logs
volumes:
neo4j_data:
neo4j_logs:
NEO4J_PLUGINS を指定すると、起動時に GDS(Graph Data Science)プラグインが自動でインストールされます。GDS は Louvain 法や PageRank などのグラフアルゴリズムを提供するプラグインで、グラフ分析を行う際に必要になります。
起動
docker compose up -d
ブラウザUIにアクセス
http://localhost:7474 にアクセスして以下でログインします。
- ユーザー名:
neo4j - パスワード:
password123
基本操作: ノードとリレーションシップの作成
Neo4j ブラウザ上部のクエリエディタに入力して Cmd+Enter で実行します。
① ノードを作成する
CREATE (alice:Person {name: "Alice", age: 30})
CREATE (bob:Person {name: "Bob", age: 25})
CREATE (carol:Person {name: "Carol", age: 35})
CREATE (acme:Company {name: "Acme Corp", industry: "Tech"})
CREATE (globex:Company {name: "Globex", industry: "Finance"})
CREATE (s1:Skill {name: "Python"})
CREATE (s2:Skill {name: "Neo4j"})
CREATE (s3:Skill {name: "Machine Learning"})
② リレーションシップを作成する
MATCH で既存ノードを検索し、CREATE でリレーションシップを作ります。
MATCH (alice:Person {name: "Alice"}), (bob:Person {name: "Bob"})
CREATE (alice)-[:KNOWS]->(bob)
MATCH (alice:Person {name: "Alice"}), (carol:Person {name: "Carol"})
CREATE (alice)-[:KNOWS]->(carol)
MATCH (bob:Person {name: "Bob"}), (acme:Company {name: "Acme Corp"})
CREATE (bob)-[:WORKS_AT {since: 2020}]->(acme)
MATCH (carol:Person {name: "Carol"}), (globex:Company {name: "Globex"})
CREATE (carol)-[:WORKS_AT {since: 2022}]->(globex)
MATCH (alice:Person {name: "Alice"}), (s1:Skill {name: "Python"})
CREATE (alice)-[:HAS_SKILL]->(s1)
MATCH (alice:Person {name: "Alice"}), (s2:Skill {name: "Neo4j"})
CREATE (alice)-[:HAS_SKILL]->(s2)
MATCH (bob:Person {name: "Bob"}), (s3:Skill {name: "Machine Learning"})
CREATE (bob)-[:HAS_SKILL]->(s3)
③ 全ノードとリレーションシップを確認する
MATCH (n)-[r]->(m) RETURN n, r, m
MATCH (n) RETURN nはノードのみ返すためグラフ表示になりません。リレーションシップも含めるには(n)-[r]->(m)の形式で取得します。
グラフビューで以下のような構造が表示されれば成功です。
(Alice) --[:KNOWS]--> (Bob) --[:WORKS_AT]--> (Acme Corp)
| |
| [:HAS_SKILL]
| |
[:KNOWS] (Machine Learning)
|
(Carol) --[:WORKS_AT]--> (Globex)
(Alice) --[:HAS_SKILL]--> (Python)
(Alice) --[:HAS_SKILL]--> (Neo4j)
プロパティの更新・削除
SET: プロパティを追加・更新する
MATCH (a:Person {name: "Alice"})
SET a.email = "alice@example.com", a.age = 31
RETURN a
REMOVE: プロパティを削除する
MATCH (a:Person {name: "Alice"})
REMOVE a.email
RETURN a
ラベルの追加・削除
// ラベルを追加
MATCH (a:Person {name: "Alice"})
SET a:Admin
// ラベルを削除
MATCH (a:Person {name: "Alice"})
REMOVE a:Admin
パターンマッチング
特定ノードを取得する
MATCH (n:Person {name: "Alice"})
RETURN n
リレーションシップをたどる(友人を取得)
MATCH (a:Person {name: "Alice"})-[:KNOWS]->(friend)
RETURN friend.name
友人の友人を取得
MATCH (a:Person {name: "Alice"})-[:KNOWS]->(friend)-[:KNOWS]->(fof)
RETURN fof.name
今回のデータでは Bob・Carol から先に :KNOWS を作っていないため0件になります。以下を追加すると確認できます:
MATCH (bob:Person {name: "Bob"}), (carol:Person {name: "Carol"})
CREATE (bob)-[:KNOWS]->(carol)
複数パターンを組み合わせる(友人が働いている会社)
MATCH (a:Person {name: "Alice"})-[:KNOWS]->(friend)-[:WORKS_AT]->(company)
RETURN friend.name, company.name
絞り込み・集計・並び替え
WHERE で条件を絞り込む
MATCH (n:Person)
WHERE n.age >= 30
RETURN n.name, n.age
ORDER BY と LIMIT
MATCH (n:Person)
RETURN n.name, n.age
ORDER BY n.age DESC
LIMIT 2
COUNT / AVG などの集計
MATCH (n:Person)
RETURN count(n) AS total, avg(n.age) AS avg_age
ラベルごとのノード数
MATCH (n)
RETURN labels(n) AS label, count(n) AS count
MATCH の挙動について
MATCH は SQL の SELECT ... FROM ... WHERE に相当し、パターンに一致するノードを検索します。
重要: MATCH が0件の場合、後続の CREATE も実行されません。クエリ全体がスキップされます。
// alice が存在しない場合、CREATE は実行されない
MATCH (alice:Person {name: "存在しない"})
CREATE (alice)-[:KNOWS]->(:Person {name: "Ghost"})
// → 0 nodes created
MATCH と MERGE の違いは以下の通りです:
| コマンド | 動作 |
|---|---|
MATCH |
一致するものを検索。なければスキップ |
MERGE |
一致するものを検索。なければ作成 |
パス探索
グラフDBの強みの1つが、ノード間の経路を効率よく探索できることです。
shortestPath で最短経路を取得
MATCH (a:Person {name: "Alice"}), (acme:Company {name: "Acme Corp"}),
p = shortestPath((a)-[*]-(acme))
RETURN p, length(p) AS hops
[*] はリレーションシップの種類を問わず全てたどることを意味します。今回のデータでは以下の経路が返ります:
(Alice) -[:KNOWS]-> (Bob) -[:WORKS_AT]-> (Acme Corp)
↑ 1ホップ目 ↑ 2ホップ目
allShortestPaths で全最短経路を取得
MATCH (a:Person {name: "Alice"}), (acme:Company {name: "Acme Corp"}),
p = allShortestPaths((a)-[*]-(acme))
RETURN p
同じ長さの最短経路が複数存在する場合、全件返します。shortestPath は1件のみ返す点が異なります。
可変長パス(k-hop探索)
[*1..N] の形式で「最大Nホップ先まで探索する」クエリを書けます。
-- 1〜2ホップで繋がるノードを全て取得
MATCH (a:Person {name: "Alice"})-[*1..2]->(n)
RETURN DISTINCT n
-- ホップ数も一緒に確認
MATCH p = (a:Person {name: "Alice"})-[*1..3]->(n)
RETURN n, length(p) AS hops
ORDER BY hops
今回のデータでは Alice から到達できるのは最大2ホップまでです:
| ホップ数 | 到達できるノード |
|---|---|
| 1ホップ | Bob, Carol, Python, Neo4j |
| 2ホップ | Acme Corp, Globex, Machine Learning |
| 3ホップ | 存在しない(Bob・Carol の先にリレーションシップがないため) |
可変長パスは、特定ノードの周辺情報を一定のホップ数内で収集する際に便利なパターンです。
HTTP API でも操作できる
curl を使って外部から操作することも可能です:
curl -X POST http://localhost:7474/db/neo4j/tx/commit \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-u neo4j:password123 \
-d '{"statements": [{"statement": "MATCH (n) RETURN n LIMIT 5"}]}'
まとめ
- Neo4j は Docker で簡単にローカル起動できる
- データはノードとリレーションシップで表現し、
CREATEで作成する -
MATCHでパターン検索、SET/REMOVEでプロパティを更新・削除する -
MATCHが0件だと後続のCREATEも実行されない点に注意 -
WHERE・ORDER BY・集計関数でデータを柔軟に絞り込める -
shortestPath()でノード間の最短経路を取得できる -
[*1..N]の可変長パスで任意のホップ数まで探索できる