cancel
Showing results for 
Search instead for 
Did you mean: 

Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.

Question/Answer Model

leelandclay
Graph Buddy

Hi,
I'm working out a design for questions and answers. The Questions will have a random number of possible entries. The users will each then be able to answer those questions. Eventually, I will be using the answer to provide matching between the users (it's a dating site).

I came up with a design of creating a :Question node that will have a property containing the Question Text. Then there would be :Answer nodes that would have the choices. These two nodes would be related. When a user answers a question, I would then create a relationship between the :Person node and the :Answer node.

I'm fairly certain that this is going to be the most efficient for the matching in the future, but I wanted to ask if there was something that I might be missing. I'm still relatively new to neo4j, so I would hate to have to rewrite my front end due to missteps at the schema design layer.

Any input would be greatly appreciated.
TIA
Leeland

1 ACCEPTED SOLUTION

I think this is a good design.

This assumes that questions have only a predefined set of possible answers, like multiple choice. Once created, the :Question and :Answer nodes should be immutable, you shouldn't need to change anything on the nodes themselves.

So you could have something like this:

(q:Question {text:"What is your favorite color?"})
(q)<-[:ANSWER_TO]-(:Answer{text:"Blue"})
(q)<-[:ANSWER_TO]-(:Answer{text:"Green"})
(q)<-[:ANSWER_TO]-(:Answer{text:"Red"})
(q)<-[:ANSWER_TO]-(:Answer{text:"Yellow"})
(q)<-[:ANSWER_TO]-(:Answer{text:"Orange"})

Then when someone answers that question, relationships are created to the question they answered and the answer they picked. You could allow them to add their own free text explaining the reasons for their answer on a property of the relationship. So you could have something like this:

(g:Person {name:"Galahad"})
(g)-[:ANSWERED]->(q:Question {text:"What is your favorite color?"})
(g)-[:CHOSE_ANSWER {text:"No wait I meant yell-auuuuuuuugh!"}]->(:Answer{text:"Blue"})

Where in this case that particular :Answer (blue) is the same blue :Answer node attached to that particular question.

This model allows you to to quickly find other persons who have answered the same questions you have:

MATCH (me:Person {name:"Lancelot"})-[:ANSWERED]->()<-[:ANSWERED]-(someoneElse)
WITH me, someoneElse, count(someoneElse) as commonQuestionCount
ORDER BY commonQuestionCount DESC
LIMIT 20
RETURN someoneElse

Or even to find the person with the highest number of the same answers picked:

MATCH (me:Person {name:"Lancelot"})-[:CHOSE_ANSWER]->()<-[:CHOSE_ANSWER]-(someoneElse)
WITH me, someoneElse, count(someoneElse) as sameAnswersPicked
ORDER BY sameAnswersPicked DESC
LIMIT 20
RETURN someoneElse

And then you can compare answers too:

MATCH (me:Person {name:"Lancelot"})-[myReason:CHOSE_ANSWER]->(answer)<-[theirReason:CHOSE_ANSWER]-(them:Person {name:"Galahad"})
WITH myReason, answer, theirReason
LIMIT 5
MATCH (answer)-[:ANSWER_TO]->(q)
RETURN q.text as question, answer.text as answer, myReason.text as myReason, theirReason.text as theirReason

I'm omitting unique ids on each of these nodes (such as uuids) but you'd want to have those (and indexed or with a unique constraint) for quick lookup.

View solution in original post

3 REPLIES 3

Hi @leelandclay, this is my 2cents. The Question and Answers are your master data sets. I wouldn't create transactions level directly to the the master data set. Tomorrow, if there are any changes to questions / answers (master data), then you have to update all the users.
instead, create an uuid for questions and answers. Then, create users relationship to the answers(with a separate label or something) uuid. this way your master data and transaction data are isolated. In the future, if you need to modify the answers, you can modify it in the master data and all the transactions will reflect the changes.

I think this is a good design.

This assumes that questions have only a predefined set of possible answers, like multiple choice. Once created, the :Question and :Answer nodes should be immutable, you shouldn't need to change anything on the nodes themselves.

So you could have something like this:

(q:Question {text:"What is your favorite color?"})
(q)<-[:ANSWER_TO]-(:Answer{text:"Blue"})
(q)<-[:ANSWER_TO]-(:Answer{text:"Green"})
(q)<-[:ANSWER_TO]-(:Answer{text:"Red"})
(q)<-[:ANSWER_TO]-(:Answer{text:"Yellow"})
(q)<-[:ANSWER_TO]-(:Answer{text:"Orange"})

Then when someone answers that question, relationships are created to the question they answered and the answer they picked. You could allow them to add their own free text explaining the reasons for their answer on a property of the relationship. So you could have something like this:

(g:Person {name:"Galahad"})
(g)-[:ANSWERED]->(q:Question {text:"What is your favorite color?"})
(g)-[:CHOSE_ANSWER {text:"No wait I meant yell-auuuuuuuugh!"}]->(:Answer{text:"Blue"})

Where in this case that particular :Answer (blue) is the same blue :Answer node attached to that particular question.

This model allows you to to quickly find other persons who have answered the same questions you have:

MATCH (me:Person {name:"Lancelot"})-[:ANSWERED]->()<-[:ANSWERED]-(someoneElse)
WITH me, someoneElse, count(someoneElse) as commonQuestionCount
ORDER BY commonQuestionCount DESC
LIMIT 20
RETURN someoneElse

Or even to find the person with the highest number of the same answers picked:

MATCH (me:Person {name:"Lancelot"})-[:CHOSE_ANSWER]->()<-[:CHOSE_ANSWER]-(someoneElse)
WITH me, someoneElse, count(someoneElse) as sameAnswersPicked
ORDER BY sameAnswersPicked DESC
LIMIT 20
RETURN someoneElse

And then you can compare answers too:

MATCH (me:Person {name:"Lancelot"})-[myReason:CHOSE_ANSWER]->(answer)<-[theirReason:CHOSE_ANSWER]-(them:Person {name:"Galahad"})
WITH myReason, answer, theirReason
LIMIT 5
MATCH (answer)-[:ANSWER_TO]->(q)
RETURN q.text as question, answer.text as answer, myReason.text as myReason, theirReason.text as theirReason

I'm omitting unique ids on each of these nodes (such as uuids) but you'd want to have those (and indexed or with a unique constraint) for quick lookup.

jsmccrumb
Graph Buddy

I'd say go right ahead with a model of :Question related to :Answer related to :Person
Each question would have many :Answer nodes related, and its up to you whether a :Person can select multiple :Answer nodes for a given :Question.

Assuming you are controlling the Questions/Answers that is, if users will be asking Questions or providing answers that could change the model (maybe not but would just be something else to think on).

Try to keep the relationship type between the nodes unique (i.e. the relationship from :Person to :Answer should not be the same as the one from :Answer to :Question) this will help your queries down the line.