Finding Nodes that do not share any paths through a grandparent node

I have Questions and Answers stored in my graph. Then, when a user answers a question, I create a relationship between their user node and the question, as well as the answer(s) they chose. Now, I'm working on creating a compare option that users can use to see how their answers compared to others. I'm creating three different queries, one for matching answers, one for non-matching answers and one for questions that the other person answered, but the user hasn't.

I've got the Matching query working. The non matching query that I came up with is:

MATCH (user1:Person {userId: 'userOne'})
MATCH (user2:Person {userId: 'userTwo'})
MATCH (user1)-[:ANSWERED]-(q:Question)-[r:ANSWERED]-(user2)
WITH user1, user2, COLLECT(q) as questions, r
UNWIND questions as q2
MATCH (q2)-[]-(o:Option)
WHERE NOT (user1)-[:ISANSWER]-(o)-[:ISANSWER]-(user2)
OPTIONAL MATCH (q2)-[]-(all:Option)
RETURN q2.questionId, q2.value, collect(all) as options, r.answer, r.acceptable, r.explanation

I can't figure out how to setup the WHERE clause to only include the questions where the users did not have ANY of the matching options. Any suggestions would be greatly appreciated right now.
TIA

does your graph have unique options for all the questions ? if not , you may not be able to know for which question , which options were selected by the user as per your design.

what is the problem you think you have with your non matching query .

you dont have to do collect and unwind. You can just do

WITH user1, user2, q , r
MATCH (q)-[]-(o:Option)
WHERE NOT (user1)-[:ISANSWER]-(o)-[:ISANSWER]-(user2)
OPTIONAL MATCH (q)-[]-(all:Option)

Perhaps you can try to count the number of Person (nPerson) who answered Option o and return only the options and questions where nPerson=1?

Each question has unique answers.
I'll try that as soon as I get a chance.

I removed the COLLECT. Thanks!

The problem that I'm having is that these are multiple choice questions. I'm trying to find the list of questions that both users have answered...but answered differently.

So, for instance, given two yes/no questions. If User 1 answers Yes to both and User 2 answers Yes to one and No to the other, the results would one question for the matching and the other question for the non-matching and zero for the "discover".

Here's the graph that I'm getting right now (blue = Person, green = Question, yellow = Option) with the non-matching query. Questions 15 and 186 do not have matching answers and should be the only results.

With each question, a user can only choose a single answer. There are "ISACCEPTABLE" relationships in the graph which aren't being used at the moment, but will be a query similar to this one.

@vkwong3988 I'm not sure I understand. I'm trying to get results where both User1 and User2 answered the same question...just selected different options.

Say you have users u1, u2, u3 answering question q1, where u1 and u2 selected option o1, but u3 selected option o2, then counting the number of users for each option should tell you that u3 selected the non-matching option.

So, this is the problem in your query. Here, you are saying that this pattern should not exist. That means , let’s say , a question has two options , yes and no , both answered Yes ..but for the same question , we have another option node , both the users are not connected to this node . So, that means your where condition is satisfied . You don’t have this pattern you mentioned in WHERE clause existing for option ‘no’ .
Because of this you will get this question also in your output . Since question is connected to the option node ( “no” here) which satisfies the condition in WHERE clause . We have to make a small tweak to your query toget the expected results .


MATCH (user1:Person {userId: 'userOne'})
MATCH (user2:Person {userId: 'userTwo'})
MATCH (user1)-[:ANSWERED]-(q:Question)-[r:ANSWERED]-(user2)
WITH user1, user2, q as q2, r
MATCH (q2)-[]-(o:Option)
WHERE (user1)-[:ISANSWER]-(o) AND NOT (o)-[:ISANSWER]-(user2)
OPTIONAL MATCH (q2)-[]-(all:Option)
RETURN q2.questionId, q2.value, collect(all) as options, r.answer, r.acceptable, r.explanation

This will give you the expected results . Since we are explicitly looking for all options which user 1 selected and not selected by user2.

WHERE (user1)-[:ISANSWER]-(o) AND NOT (o)-[:ISANSWER]-(user2)

This is the change I made .

And this will only work when user is allowed to select only one option per question . Let me know if users can select multiple , we will update the query to handle it

Thank You!!!!!
When I was reading your explanation my brain was screaming..."Yes...that's exactly what I'm trying to do, but how???" LOL
Then when I saw the separation of the WHERE clause it was like "oh...yeah...duh". But for some reason, my brain never made that jump. Thanks!

You're welcome.
Glad it helped :slight_smile: .
But remember this won't work when users can select multiple options for each question.

Thanks. I'm not foreseeing any need for them to choose multiple answers, but I am curious how multiple answers could be handled. If you have time and don't mind explaining it, that is.

MATCH (user1:Person {userId: 'userOne'}) MATCH (user2:Person {userId: 'userTwo'}) MATCH (user1)-[:ANSWERED]-(q:Question)-[r:ANSWERED]-(user2) WITH user1, user2, q as q2, r MATCH (q2)-[]-(o:Option)-[ISANSWER]-(user1) WITH q2,user1, COLLECT (o) as answers WHERE NONE(o in answers WHERE (o)-[:ISANSWER]-(user2)) OPTIONAL MATCH (q2)-[]-(all:Option) RETURN q2.questionId, q2.value, collect(all) as options, r.answer, r.acceptable, r.explanation

This is for NONE of options selected by user 1 and user 2 will match..you can use this query for single option too.
Now with multiple options , you can have partial matches ,and jaccard similarity can be useful to find match score

MATCH (q : question) -[]-(o:Option) WITH q, COLLECT (o) AS options MATCH (user1:Person {userId: 'userOne'})-[ISANSWER]-(o:Option) WHERE o IN options MATCH (user2:Person {userId: 'userTwo'})-[ISANSWER]-(o2:Option) WHERE o2 IN options Return q2.questionId, q2.value, options , algo.similarity.jaccard ( collect(id(o1)), collect (id(o2))) as matchScore

1 Like

Thanks. I always forget about the algo and apoc packages. I should probably write a note and stick it on my monitor lol

I recreated your scenario and here is with one mismatch:

Find one mismatch:

match (q)-[:ISOPTION]-(b:Answer)-[:ISANSWER]-(u1:User)
where u1.name = "U1"

match (q)-[:ISOPTION]-(a:Answer)-[:ISANSWER]-(u2:User)
where u2.name = "U2" and a.choice <> b.choice

match (q)-[:ISOPTION]-(c)-[:ISANSWER]-(u1)
return q, a, c, u2, u1

Result:

Find two mismatches:

Use same query as above and the result is: