Pythonでグラフデータベースであるneo4jを操作する方法について解説します。
Contents
グラフデータベース neo4j
neo4jとは、NeoTechnology社が提供しているグラフデータベースのことを言います。
データベースというとまず最初に思い浮かべるのはリレーショナルデータベース(RDB)かと思います。近年では、キー・バリュー型、列指向型、ドキュメント型などの様々なタイプのデータベースが登場しています。グラフデータベースもそういったデータベースの一種です。
グラフデータベースとは、以下の例のように「ノード」と「リレーションシップ」という要素で構成され、要素と関係性をグラフで表現します。(※このグラフは以降の説明の操作で作成します。)
RDBは、列と行でレコードを表現しますが、グラフデータベースであるneo4jでは、ノード間の関連をリレーションシップという形で紐づけて表現することになります。ネットワーク構造を持つようなデータを直感的に表現できるという利点があります。
また、neo4jではデータベース操作用言語として、Cypher Queryという言語をサポートします。Cypher QueryはSQLに似た形式となっておりデータのアクセスや表現を行うことができます。簡単な例については以降で紹介します。
本記事では、まずneo4jをインストールし、Cypher Queryでグラフデータベースを操作してみます。その後、Pythonプログラムからアクセスして操作する方法について説明します。
neo4jの公式ページはこちらを参考にしてください。
neo4jの利用方法とグラフデータベース作成
neo4jの利用方法
neo4jを利用するにはneo4jの公式ページにアクセスします。そこで、「Start Free for Developers」をクリックしてください。(※公式ページの構成は変わっている可能性があります)
neo4jはクラウドサービスとして使用できます。本記事での目的は学習目的で使ってみることなので、AuraDB Freeというところの「Start Free」をクリックします。こちらはクレジットカード情報など不要です。用途によってProfessionalやEnterpriseの使用を検討してみてください。
Emailアドレス、パスワードの入力をして「Register」をクリックします。
Emailの確認が必要でメールが送付されます。メールに記載の「Verify my e-mail address」というボタンを押して確認を完了させてください。メールが届かない場合は、以下のResendを押してみてください。
Emailの確認後に上記画面のDashboardをクリックするとサービスとプライバシーポリシーの確認が出るので、確認して「I agree」をクリックしてください。
以上で、neo4jを使用する準備が整います。
neo4jデータベースを作成する
Dashboardを最初に開くと「Let’s create your first database」ということで最初のデータベースを作成するページが開かれるかと思います。Database detailsのDatabase Nameの部分に任意のデータベース名を入力して、「Create Database」をクリックしてください。
上記を実行するとデータベースを作成することができます。
データベースの資格情報として以下のようにUsernameとPasswordが表示されるます。後の接続で必要になりますので控えてから「Continue」をクリックしてください。
以下のようなデータベースが作成されればデータベースの作成は完了です。Runningとなっているので起動状態となっています。
neo4jのグラフデータベース操作用言語 Cypher Query
WebベースのDB管理ツール
上記までで、グラフデータベースを作成することができました。データベースのQueryをクリックすると、WebベースのDB管理ツール画面が表示でき、neo4jの操作用言語であるCypher Queryを実行することができるようになります。
以下のようなConnect画面が出ますので、データベース作成時に控えたUsername、Passwordを入力して「Connect」をクリックしてください。
接続できると以下のようにコマンドを入力できるようなコンソール画面になります。neo4jでは、ここにCypher Queryを入力することでデータベースを操作することができます。
Cypher Queryによるデータの操作例
Cypher Queryを使ってneo4jを操作する例を簡単な例を使ってみてみましょう。以降のコマンドは、上記のWebの管理ツールのコンソール上にコマンドを打って実行してみます。
ノードの追加
まずは、簡単にノードを追加してみます。以下のようなCypher Queryを入力して「▶」をクリックまたは「Ctrl+enter」を押して実行します。
CREATE (p:Person {name: "taro"}) RETURN p
実行すると、以下のようにtaroというノードが作成されます。
Cypher Queryの構成としては、Personという種類のノードで、属性としてname=”taro”を持っているものを作成(CREATE)しているということになります。RETURNの部分は表示するものを指定していると思ってもらえればいいかと思います。
リレーションシップの追加
次にノードとノードをつなぐリレーションシップを作成してみます。以下のようなCypher Queryを入力して実行します。
MATCH (p:Person {name:"taro"}) CREATE (p)-[:FRIEND]->(hanako:Person {name:"hanako"}) RETURN p,hanako
上記のコマンドは、まずtaroをMATCHでpに取得してきて、hanakoとFRIENDというリレーションシップでつなぐという意味になっています。実行すると、以下のようにhanakoというノードが作成されるとともにFRIENDという関係の矢印が作成されます。
リレーションシップについては、Cypher Queryで「()-[]->()」という形式を使うことでノード間を接続します。[]の部分がリレーションシップの定義です。今回の例では[:FRIEND]というのはFRIENDという種類の関係性を作成していることを意味しています。
繰り返しを使用する
Cypher Queryでは繰り返しで関係性を作ることも可能です。以下のようなCypher Queryを実行します。
MATCH (p:Person {name:"taro"}) FOREACH (name in ["jiro", "haruka", "sakura"] | CREATE (p)-[:FRIEND]->(:Person {name:name}))
上記を実行すると「Added ~」というような形で表示がされると思います。その後、以下で全てのノードと関係性を表示してみてください。
MATCH (n) OPTIONAL MATCH (n)-[r]-() RETURN n,r
以下の図のようにtaroから各ノードへの関係性が作成されたのが分かるかと思います。
プログラミングにある程度慣れている方なら直感的にわかるかと思いますが、inに指定しているリストの中身が順にnameに設定され、CREATEで設定しているname属性の値として使用されています。Pythonのfor _ in ~という形と同じですので、Pythonプログラムを作ったことがある方であれば分かりやすいのではないかと思います。
全てのノードを検索する
既に上記で出てきていますが、データベース上の全てのノードを検索するには、以下のようなCypher Queryを実行します。よく使う方法として覚えておくとよいでしょう。
MATCH (n) OPTIONAL MATCH (n)-[r]-() RETURN n,r
全てのノードを削除する
データベース上の全てのノードを削除するには、以下のようなCypher Queryを実行します。データを一度リセットしたい場合に使用できます。上記のサンプルを実行していてよく分からなくなった場合は、一度全て削除してみて再度操作してみてください。
MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r
Cypher Query Languageについては、neo4jのDeveloper Guidesにあるこちらのページを参考にしてください。
Pythonからneo4jグラフデータベースを操作する
上記まででneo4jのインストール方法とCypher Queryによるデータベースの簡単な操作方法について紹介してきました。以降では、Pythonプログラムから直接neo4jのデータベースを操作する方法について紹介します。
Pythonで使用するためにneo4jをインストールする
Pythonでneo4jを使用するには、以下のようにpip installでインストールを実行します。
pip install neo4j
正常に完了すればneo4jをPythonで使用する環境が整います。
Pythonでneo4jを使用するサンプルプログラム
neo4jにPythonでアクセスするためのサンプルプログラムを以下に示します。
接続情報はneo4j.iniというファイルに記載して、configparserで読み込む形式にしています。以下の記載は接続情報などはXXXというように伏せているため皆さんの環境の接続情報に書き換えて使ってみてください。
【設定ファイル】neo4j.ini
[NEO4J] uri = neo4j+s://xxxxxxxxxxxxxxxx:7687 user = xxxxxx password = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
【サンプルプログラム】neo4j_sample.py
import configparser from neo4j import GraphDatabase def clear_db(tx): tx.run('MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r') def search_all(tx): result = tx.run('MATCH (n) OPTIONAL MATCH (n)-[r]-() RETURN n,r') return [r for r in result] def add_person_node(tx, name): tx.run('CREATE (p:Person {name: $name}) RETURN p', {'name': name}) def add_friend_relationship(tx, name, friend_name=None): if not friend_name: tx.run('CREATE (p:Person {name: $name}) RETURN p', {'name': name}) else: tx.run('MATCH (p:Person {name: $name}) ' 'CREATE (p)-[:FRIEND]->(:Person {name: $friend_name})', name=name, friend_name=friend_name) def main(): # ===== neo4jの設定取得 config = configparser.ConfigParser() config.read('neo4j.ini') uri = config['NEO4J']['uri'] user = config['NEO4J']['user'] password = config['NEO4J']['password'] # neo4jドライバーの作成 driver = GraphDatabase.driver(uri, auth=(user, password)) with driver.session() as session: # データベースのクリア session.write_transaction(clear_db) # ノードの追加 session.write_transaction(add_person_node, 'taro') # FRIEND関係の追加 session.write_transaction(add_friend_relationship, 'taro', 'hanako') # 繰り返しで追加 friend_list = ['jiro', 'haruka', 'sakura'] for f in friend_list: session.write_transaction(add_friend_relationship, 'taro', f) # データの検索 result = session.read_transaction(search_all) # 結果の確認 for res in result: print(res) if __name__ == '__main__': main()
【実行結果】 <Record n=<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}> r=<Relationship id=42 nodes=(<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}>, <Node id=67 labels=frozenset({'Person'}) properties={'name': 'sakura'}>) type='FRIEND' properties={}>> <Record n=<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}> r=<Relationship id=41 nodes=(<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}>, <Node id=66 labels=frozenset({'Person'}) properties={'name': 'haruka'}>) type='FRIEND' properties={}>> <Record n=<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}> r=<Relationship id=40 nodes=(<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}>, <Node id=65 labels=frozenset({'Person'}) properties={'name': 'jiro'}>) type='FRIEND' properties={}>> <Record n=<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}> r=<Relationship id=39 nodes=(<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}>, <Node id=64 labels=frozenset({'Person'}) properties={'name': 'hanako'}>) type='FRIEND' properties={}>> <Record n=<Node id=64 labels=frozenset({'Person'}) properties={'name': 'hanako'}> r=<Relationship id=39 nodes=(<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}>, <Node id=64 labels=frozenset({'Person'}) properties={'name': 'hanako'}>) type='FRIEND' properties={}>> <Record n=<Node id=65 labels=frozenset({'Person'}) properties={'name': 'jiro'}> r=<Relationship id=40 nodes=(<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}>, <Node id=65 labels=frozenset({'Person'}) properties={'name': 'jiro'}>) type='FRIEND' properties={}>> <Record n=<Node id=66 labels=frozenset({'Person'}) properties={'name': 'haruka'}> r=<Relationship id=41 nodes=(<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}>, <Node id=66 labels=frozenset({'Person'}) properties={'name': 'haruka'}>) type='FRIEND' properties={}>> <Record n=<Node id=67 labels=frozenset({'Person'}) properties={'name': 'sakura'}> r=<Relationship id=42 nodes=(<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}>, <Node id=67 labels=frozenset({'Person'}) properties={'name': 'sakura'}>) type='FRIEND' properties={}>>
【実行後neo4jの状態】
実行結果を管理ツールで見てみると前の節で順番にCypher Queryを実行したのと同じグラフができていることが分かります。コンソール上の出力結果では、グラフに出てくる全てのノードと関係性が列挙されます。
以降でサンプルの説明をしていきます。
Pythonからneo4jへ接続する
neo4jを使用するためには、neo4jパッケージからGraphDatabaseをimportします。
from neo4j import GraphDatabase
以下の部分はneo4j.iniから接続情報を取得している部分です。neo4jというよりconfigparserモジュールの使い方というところですので、configparserの使い方が分からない方は「configparserによるconfigファイル管理」も参考にしてください。
# ===== neo4jの設定取得 config = configparser.ConfigParser() config.read('neo4j.ini') uri = config['NEO4J']['uri'] user = config['NEO4J']['user'] password = config['NEO4J']['password']
neo4jに接続するには、以下の記載の部分のようにドライバーを取得し、sessionにより接続します。
driver = GraphDatabase.driver(uri, auth=(user, password)) with driver.session() as session:
write_transactionでneo4jに書き込みを行う
neo4jへ書き込みを行う場合には、neo4jのセッションを作成した後にwrite_transactionを使用することで実行できます。
with driver.session() as session: # データベースのクリア session.write_transaction(clear_db) # ノードの追加 session.write_transaction(add_person_node, 'taro') # FRIEND関係の追加 session.write_transaction(add_friend_relationship, 'taro', 'hanako') # 繰り返しで追加 friend_list = ['jiro', 'haruka', 'sakura'] for f in friend_list: session.write_transaction(add_friend_relationship, 'taro', f)
具体的には、処理に関する関数とその関数に渡す引数を渡します。関数自体を渡すことになりますので、session.write_transaction(clear_db)というようにclear_db関数に()はないことに注意しましょう。例えばclear_dbはデータベースをすべて削除する関数です。
具体的に渡す関数として実装しているのは以下の部分です。
def clear_db(tx): tx.run('MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r') def add_person_node(tx, name): tx.run('CREATE (p:Person {name: $name}) RETURN p', {'name': name}) def add_friend_relationship(tx, name, friend_name=None): if not friend_name: tx.run('CREATE (p:Person {name: $name}) RETURN p', {'name': name}) else: tx.run('MATCH (p:Person {name: $name}) ' 'CREATE (p)-[:FRIEND]->(:Person {name: $friend_name})', name=name, friend_name=friend_name)
関数はtxという引数を受け取るように作成します。neo4jモジュールのwrite_transactionの方でtxが設定され、tx.runによりCypher Queryを実行します。clear_dbは単純にデータを全て削除するCypher Queryをtx.runに渡していることが分かるかと思います。
一方で、add_person_nodeやadd_friend_relationshipという方は、txの他に引数を受け取っています。受け取った引数は、Cypher Query上で$name、$friend_nameのように指定することで、受け取った引数を当てはめてCypher Queryを実行できます。Oracle等のDBMSをプログラムから扱ったことがある人であればバインド変数と思っていただければわかるかと思います。
read_transactionでneo4jからデータを取得する
neo4jの読み込みを行う場合には、neo4jのセッションを作成した後にread_transactionを使用することで実行できます。
with driver.session() as session: # データの検索 result = session.read_transaction(search_all) # 結果の確認 for res in result: print(res)
read_transactionに渡している関数であるsearch_allは以下の部分で定義しています。
def search_all(tx): result = tx.run('MATCH (n) OPTIONAL MATCH (n)-[r]-() RETURN n,r') return [r for r in result]
考え方は上記のwrite_transactionと同様です。全データ検索用のCypher Queryをtx.runに渡しています。結果は「’neo4j.work.result.Result’」というもので返却されますが扱いやすいようにリスト内包表記でリストに返却して返しています。
以上が、Pythonでのneo4jでの簡単な使い方です。write_transactionやread_transactionに渡す関数を色々変えてもらえば任意のデータベース操作関数や専用のクラスを作成するといったことも可能になります。色々試してみてください。
関数を渡すというのは高階関数(関数自体を引数/戻り値として扱う関数)について勉強したことがないと分かりにくいかもしれません。「デコレータ(decorator)の基本的な使い方」にてデコレータの説明をする過程で高階関数やクロージャの説明を含めて説明しているため参考にしてみてください。