Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
6
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

@dowanna6

cypher:複数のmatchと、matchをカンマで繋がる違いが分からなくて頭を机に打ち付けたのはどう考えてもお前らが悪い!

挨拶

初めまして!株式会社プラハCEO兼エンジニアの dowanna です!
普段は会社経営とか会社のみんなで沖縄旅行をしていますが
「そういえば俺エンジニアだったな」と思い出したので、advent calendarに初参加してみました。
生暖かい目で見ていただけると喜びます。

本題

neo4jを使っているとcypherクエリ、特にmatchクエリを繋ぐ事がありますよね。

「cypherって、直感的でわかりやすいじゃん!」
そんな風に考えていた時期が、僕にもありました。

でも少しずつ複雑なクエリを書き始めると、意図しないmatchの挙動に悩まされて頭を机に打ち付けたくなるので、同じところで誰かが躓かないよう備忘録を残しておきます。

複数のmatchと、カンマつなぎのmatchの違いがわからない

特に僕が当初よく理解できなかったのが

match hogehoge
match fugafuga

と「matchクエリを」繋げる方法(パターン1)と

match hogehoge, fugafuga

と「カンマでクエリ自体」繋げる方法(パターン2)の違いです。

これ同じことじゃないの??と思っていました。

というわけで実験してみましょう。

データの準備

実験用の簡単なデータを作ります。

create (a:User{id:'1'}),(b:User{id:'2'}),(c:User{id:'3'})
create (a)-[:KNOWS]->(b)-[:KNOWS]->(c)

こんなグラフができます。

image.png

実験1:特定のnodeを取得する

では、試しにnodeを(パターン1)で取得してみましょう

match (a:User{id:'1'})
match (b:User{id:'2'})
return a.id, b.id

image.png

試しにnodeを(パターン2)で取得してみましょう

match (a:User{id:'1'}), (b:User{id:'2'})
return a.id, b.id

image.png

同じ結果が得られました。

「なんだ、やっぱり複数のmatchも、matchのクエリつなぎも同じやんけ」

と思いきや、patternマッチになると話が変わってきます

実験2:patternを取得する

まずは(パターン1=複数のmatch)で取得してみましょう。

match (a:User{id:'1'})-[:KNOWS]-(b:User)
match (a:User{id:'1'})-[:KNOWS*1..2]-(c:User)
return a.id, b.id, c.id

こんなクエリを書くことは少ないかもしれませんが、やっている事を要約すると:

①「1さんが1次的に繋がっているユーザを探す」
②「1さんが1〜2次的に繋がっているユーザを探す」
③「そいつらのidを返す」

(パターン1=複数のmatch)の結果はこちら
image.png

割と直感的というか、意図した通りの結果が返ってくるのが分かります。

では(パターン2=matchをカンマで繋ぐ)で取得してみましょう。

match (a:User{id:'1'})-[:KNOWS]-(b:User), (a:User{id:'1'})-[:KNOWS*1..2]-(c:User)
return a.id, b.id, c.id

image.png

あれ!?no records!?なんで!?

説明:matchをクエリで繋ぐと、一つのクエリと見なされる

cypherのmatchには「同じmatchの中で一度出てきたパターンは2度と出てこない」という仕様が存在します。
つまりパターン2を図解すると(画質悪くて申し訳ないです)

①「1さんが1次的に繋がっているユーザを探す」
②「1さんが1〜2次的に繋がっているユーザを探す」
③「そいつらのidを返す」

まず①「1さんが1次的に繋がっているユーザを探す」ため、赤矢印のパスを通ります。

image.png

次に②「1さんが1〜2次的に繋がっているユーザを探す」ため、また1さんを起点にユーザを辿ろうとします。
これを青矢印②としましょう。

image.png

しかしすでに①のパターンが出現しているため、「同じmatchの中で一度出てきたパターンは2度と出てこない」仕様により、このパターンが返ってくる事無く、matchクエリが終了します。

なので(パターン2=matchをカンマで繋ぐ)を実行した場合、
①「1さんが1次的に繋がっているユーザを探す」は2さんを返しますが
②「1さんが1〜2次的に繋がっているユーザを探す」はno recordsとなります。

cypherのmatchクエリをカンマでつないだ場合、後に実行された結果が返却されるため、②のno recordsが返されます。だから何も取得できません。

説明:matchを複数実行すると、別々のクエリと見なされる

では(パターン1=複数のmatch)の場合、何が起きているのか。

match (a:User{id:'1'})-[:KNOWS]-(b:User) // ①
match (a:User{id:'1'})-[:KNOWS*1..2]-(c:User) // ②
return a.id, b.id, c.id

①「1さんが1次的に繋がっているユーザを探す」
②「1さんが1〜2次的に繋がっているユーザを探す」
③「そいつらのidを返す」

処理の流れはパターン2と同じですが、カンマ区切りとは異なり、それぞれのmatchが独立したクエリとして解釈される点に注意が必要です。

まず①で赤矢印のパスを辿ります。

image.png

ここでmatchクエリが一回終了します。赤矢印が消えるようなイメージです。

match (a:User{id:'1'})-[:KNOWS]-(b:User) // ① ⇦このクエリは終了
match (a:User{id:'1'})-[:KNOWS*1..2]-(c:User) // ②
return a.id, b.id, c.id

image.png

そして新しいクエリとしてパスを辿りはじめます。青矢印ですね。
今度は「これまで一度も出てきた事のないパターン」なので、その先に進めます。

image.png

だからパターン1の結果には3が含まれているんですね。
パターン1は別々のmatchクエリだから、一つ目のmatchクエリのパターンが、二つ目のmatchクエリのパターンに影響しない。
パターン2は同じmatchクエリだから、一つ目のmatchクエリのパターンが、二つ目のmatchクエリのパターンに影響する

これが今回伝えたかったことです。

タイトルには「お前らが悪い!」と書きましたが、matchの仕様をちゃんと読み解いていなかった僕が悪い・・・いやでもtutorialでちゃんと言及しないお前らが・・・ドキュメントにも記載しないお前らが・・・ウゥッ!!!

まとめ

  • matchを複数繋ぐクエリと、matchの中身をカンマで繋ぐクエリには明確な違いがある
  • カンマで繋いだ場合、一つのmatchクエリと見なされる
  • 「matchクエリ中、同じパターンは2度と返ってこない」仕様を意識しておくと、正しく使い分けられる
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
6
Help us understand the problem. What are the problem?