LoginSignup
1
1

More than 3 years have passed since last update.

cypherで「特定のnodeと同じ繋がりを持つnode」を探す

Posted at

何がしたかったのか

例えばこんなグラフDBのデータがあったとします

CREATE
(q1:Question{questionId: randomUUID()}),
(q1)<-[:BELONGS_TO]-(a1:Answer{choice:1}),
(q1)<-[:BELONGS_TO]-(a2:Answer{choice:2}),
(q1)<-[:BELONGS_TO]-(a3:Answer{choice:3}),
(q1)<-[:BELONGS_TO]-(a4:Answer{choice:4}),

(q2:Question{questionId: randomUUID()}),
(q2)<-[:BELONGS_TO]-(a5:Answer{choice:1}),
(q2)<-[:BELONGS_TO]-(a6:Answer{choice:2}),
(q2)<-[:BELONGS_TO]-(a7:Answer{choice:3}),
(q2)<-[:BELONGS_TO]-(a8:Answer{choice:4})

4択問題があって、その質問(Question)と回答(Answer)が紐づいています。
とあるユーザが4択問題に回答した時は、こんなクエリを実行します

MATCH
(u:User{userId:'hoge'})

CREATE
(u)-[:CHOSE]->(a1),
(u)-[:CHOSE]->(a6)

これでユーザーhogeさんが、質問1に対して「1」、質問2に対して「2」と答えた状態が保存されます。

ユーザーhogeさんと全問同じ回答のユーザを取得したい

これが今回の目的です。

何も考えずに書いてみる

MATCH
(u:User{userId:'hoge'})-[:CHOSE]->(a:Answer),
(a)<-[:CHOSE]-(users:User)
RETURN
users

これだと「hogeさんと一つでも同じ回答をしたユーザ」を取得してしまいますから、「全問同じ回答のユーザ」ではありませんね。

WHERE ALL を使う

MATCH (u:User{userId:"hoge"})-[:CHOSE]->(a:Answer)
WITH collect(a) as answers
WHERE ALL(answer in answers WHERE (u2:User)-[:CHOSE]-(answer))
return u2

ここでWITHとALL()を使ってみます。

ALL()は、LISTの全要素が与えられた条件を満たす場合にtrueを返します。
それを応用して、ここではhogeさんが選んだAnswerのLISTの全要素に対して[:CHOSE]を貼っているユーザを抽出するというクエリを書いています。

ALL()を使うためにはLIST化する必要があるので、collect(ノード) as answers(LIST)で前処理を施しています。

これで表題の問題は解決です。

もうちょっとクエリの効率を改善する

この状態だとクエリの効率が悪いので、少しだけ改善を試みます。

MATCH (u:User{userId:"1234"})-[:CHOSE]->(a:Answer)
WITH collect(a) as answers
WITH head(answers) as head, tail(answers) as answers
MATCH (u2:User)-[:CHOSE]-(head)
WHERE ALL(answer in answers WHERE (u2)-[:CHOSE]-(answer))
return u2

追加したのは以下2行です

WITH head(answers) as head, tail(answers) as answers
MATCH (u2:User)-[:CHOSE]-(head)

head()は与えられたLISTの最初の要素のみ抽出し、tail()は与えられたLISTの最初の要素以外を抽出します。(なんかセットに使われる事を前提に作られたリスト関数に見えるけど、tailを独立して使う事あるのかな・・・)

要はなんでもいいからhogeさんの回答と同じ回答を1つ持つユーザに、最初に絞っておくことで、一問でも異なる回答をしたユーザを最初に弾いて、後の計算量を減らしています。2択問題だとユーザが絞りきれないのでイマイチかもしれませんが、1〜10のスケールとか、一致する確率が低い問題になるほど効率化が図れるのではないでしょうか。

まとめ

  • all()を使う
  • head/tailを使うと、LISTマッチ系のクエリは少し効率化できる場合がある

以上、apoc使わずintersectionを実装するとこんな感じだよ、という紹介でした

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1