Relations to relation neo4j - neo4j

Maybe it is a long shot but worth trying...
I have the following relation User1-[:MATCHED]-User2, I want to allow other users to give feedback (Like) on that relation, I am guessing that the obvious answer is to define new node from type Match which will be created for every two matched users and then relate to that node with LIKE relation from each user who liked the match.
I am trying to think about other way to model that in the graph without the overhead of creating new node for each match...
Can a relation relate to other nodes except the start/end nodes?
Any help will be appreciated thanks.

Neo4j does not support hypergraphs or relationships to relationships. Modelling your MATCHED relationship with a node is probably the way to go.
An alternative is to reference the relationship id from another node:
User1-[MATCHED]->User2 (where MATCHED has the id xyz)
User3-[LIKES]->Relationship(relId = xyz)
The "Relationship" node would contain the id of the MATCHED relationship as a property. This relId property would need to be indexed to find all LIKES of a given MATCHED relationship.
This solution is not well suited for traversals though.

Related

Developing graph database model for department/supplier/items

I'm currently ramping up on graph databases and to do that am working through a set of questions to learn Cypher. However, I'm not 100% happy with the design I've chosen since I have to match relationships to nodes to make some of the queries work.
I found Neo4j: Suggestions for ways to model a graph with shared nodes but has a unique path based on some property with some suggestions that are relevant, but they involve copying nodes (repeating them) when in fact they do represent the same thing. That seems like an update issue waiting to happen.
My design currently has
(:Dept {name,floor})-[:SOLD {quantity}]->(:Item {name,type})<-[:SUPPLIES {dept,volume)]-(:Company {name,address})
As you can see, to figure out which department a company supplied an item to, I have to check the :SUPPLIES dept property. This leads to somewhat awkward queries - it feels that way to me, anyway.
I've tried other relationships, like having (:Company)-[:SUPPLIES {item,vol}]->(:Dept) but then the problem just shifts to matching :SUPPLIES relationship properties to :Item nodes.
The types of queries I am building are of the nature: Find departments that sell all of the items they are supplied.
Is there some other way to model this that I am overlooking? Or is this sort of relationship, where a supplier is related to two things, an item and a department, just something that doesn't fit the graph model very well?
You want to store and query a triangular relationship between :Dept, :Item, and :Company. This can't be accomplished by a linear relationship pattern. Comparing IDs of entities is not the Neo4j way, you would neglect the strengths of a graph database.
(Assuming that I understood your use case scenario) I would introduce an additional node of type :SupplyEvent that has relationships to :Dept, :Item, and :Company. You could also split up :SOLD relationship in a similar way, if you want relations between department, item, and, e.g., a customer.
Now, you can query all companies that supplied which items to which departments (without comparing any IDs):
MATCH (company:Company)<-[:SUPPLIED_FROM]-(se:SupplyEvent)-[:SUPPLIED_TO]->(dept:Dept),
(se)-[:SUPPLIED]->(item:Item)
RETURN company, item, dept

Should I avoid creating two relations between two nodes in unidirectional mapping?

I've already understood that in order to get unidirectional mapping I simply can ignore direction when writing queries. However, I have a question about inserting data.
UserA -[friend]-> UserB
UserA <-[friend]- UserB
Should I avoid create secondary friend relation in other direction? If so, how can I achieve this?
I mean, how to write in cypher "creation relation only of there is no same relation in other direction"?
You want to MERGE the relationship. Unlike CREATE, you can omit the direction of a merged relationship:
MERGE (userA)-[:FRIEND]-(userB)
This will check for an existing relationship in either direction, and if none exists it will create one with an arbitrary direction.
As for whether to create a second relationship in the opposite direction, avoid that if you can, unless it has a distinct meaning compared to the first relationship.
For example, a person can be in love with another person, but that doesn't mean the other person loves them back, a second relationship would be needed to cement that.

Creating a relationship to a relationship in Neo4j

Is it possible to create a relationship to a relationship in Neo4j?
The use-case goes something like this:
I have a bunch of questions, like "What movie should we see?"
Each question can have many options like "Movie1", "Movie2", etc.
For each question I want a user to be able to vote for their favorite option.
The graph would preferably look something like this:
(:Question {name:"What movie?"})-[:Option]->(:Movie {name:"Movie1"})
^
|
[:Vote]
|
(:User)
I realize that one way I could solve this is with the following:
(:Question)-[:Option]->(:Movie)<-[:Vote]-(:User)
But then if I decide to remove the Movie as an Option in the future, I don't get to take advantage of DETACH and will have to manage removing the Vote relationship myself. Not to mention, if the Movie belongs to multiple categories, I have to keep track of which Question->Movie relationship it belongs to (probably with some sort of ID). It just seems very messy...
Is is possible to create a relationship to a relationship? Or am I going to have to manually enforce referential integrity?
Is is possible to create a relationship to a relationship?
No. This is not possible. According the docs:
A relationship connects two nodes, and is guaranteed to have a valid
source and target node.
That is: the start and end point of a relationship should be a node.
I believe you should do some changes in your data model. For example:
Maybe the Option can be a node and not a relationship. Make more sense, not? This way:
(:Category)-[:HAS]->(:Option)
Also, the Vote can be a node too, and not a relationship... This way, when the user makes (a relationship, ahn?) a vote, this vote node will reference the option and the category that it is relative to.
(:Category)-[:HAS]->(:Option)
\ /
[:FOR_CATEGORY][:FOR_OPTION]
\ /
(:Vote)
|
[:MAKES]
|
(:User)
If, for example, you need to delete a Option and consequently the :Votes related to it you can do something like:
MATCH (o:Option {id:10})<-[:FOR_OPTION]-(v:Vote)
DETACH DELETE o
DETACH DELETE v
Make any sense? Sorry for my ASCII art. :)
I'm investigating this. The only reasonable way I can find is to assign an identifier to the relationship (:HAS in your case) and then use it in the pointing relationship (:VOTE).
Neo4j has internal IDs for this (see the ID() function), but I usually prefer to try to assign a semantically meaningful ID (like a person's national insurance number, a page URL or, in case of relations, the hash code of its concatenated endpoint's identifers).

Neo4J - Prevent duplicated relationship types with identical meaning

Consider Person nodes and Item nodes.
What is the best way to prevent having both 'Purchased' type relationships and 'Bought' type relationships in the graph that have the same meaning, but are simply named differently?
E.g. if we end up with our graph in a state like:
(Alice) -[Bought] -> (Pickles)
(Bob) -[Purchased]-> (Pickles)
and I want to know everyone who has bought a jar of pickles. Clearly someone made a mistake when creating one of these relationships. How do I prevent that class of mistake?
Limit the relationships a user can create to a specific set of names, and don't allow any other relationship names.

Relating an entity to a relationship proper in Neo4j?

I'm attempting to use Neo4j to model the relationship between projects, staff, and project roles. Each project has a role called "project manager" and a role called "director". What I'm trying to accomplish in the data model is the ability to say "for project A, the director is staff X." For my purposes, it's important that "project", "staff", and "role" are all entities (as opposed to properties). Is this possible in Neo4j? In simpler terms, are associative entities possible in Neo4j? In MySQL, this would be represented with a junction table with a unique id column and three foreign key columns, one for project, staff, and role respectively, which would allow me to identify the relationship between those entities as an entity itself. Thoughts?
#wassgren's answer is a solid one, worth considering.
I'll offer one additional option. That is, you can "Reify" that relationship. Reificiation is sort of when you take a relationship and turn it into a node. You're taking an abstract association (relationship between staff and project) and your'e turning it into a concrete entity (a Role) All of the other answer options involve basically two nodes Project and Staff, with variations on relationships between them. These approaches do not reify role, but store it as a property, or a label, of a relationship.
(director:Staff {name: "Joe"})-[:plays]->(r:Role {label:"Director"})-[:member_of]->(p:Project { name: "Project X"});
So...people don't contribute to projects directly, roles do. And people play roles. Which makes an intuitive sense.
The advantages of this approach is that you get to treat the "Role" as a first-class citizen, and assert relationships and properties about it. If you don't split the "Role" out into a separate node, you won't be able to hang relationships off of the node. Further, if you add extra properties to a relationship that is masquerading as a role, you might end up with confusions about when a property applies to the role, and when it applies to the association between a staff member and a project.
Want to know who is on a project? That's just:
MATCH (p:Project {label: "Project X"})<-[:member_of]-(r:Role)<-[:plays]-(s:Staff)
RETURN s;
So I think what I'm suggesting is more flexible for the long term, but it might also be overkill for you.
Consider a hypothetical future requirement: we want to associate roles with a technical level or job category. I.e. the project manager should always be a VP or higher (silly example). If your role is a relationship, you can't do that. If your role is a proper node, you can.
Conceptually, a Neo4j graph is built based on two main types - nodes and relationships. Nodes can be connected with relationships. However, both nodes and relationships can have properties.
To connect the Project and Staff nodes, the following Cypher statement can be used:
CREATE (p:Project {name:"Project X"})-[:IS_DIRECTOR]->(director:Staff {firstName:"Jane"})
This creates two nodes. The project node has a Label of type Project and the staff node has a Label of type Staff. Between these node there is a relationhip of type IS_DIRECTOR which indicates that Jane is the director of the project. Note that a relationship is always directed.
So, to find all directors of all project the following can be used:
MATCH (p:Project)-[:IS_DIRECTOR]->(director:Staff) RETURN director
The other approach is to add properties to a more general relationship type:
create (p:Project {name:"Project X"})<-[:MEMBER_OF {role:"Director"}]-(director:Staff {firstName:"Jane"})
This shows how you can add properties to a relationship. Notice that the direction of the relationship was changed for the second example.
To find all directors using the property based relationship the following can be used:
MATCH (p:Project)<-[:MEMBER_OF {role:"Director"}]-(directors:Staff) RETURN directors
To retrieve all role types (e.g. director) the following can be used:
MATCH
(p:Project)-[r]->(s:Staff)
RETURN
r, // The actual relationship
type(r), // The relationship type e.g. IS_DIRECTOR
r.role // If properties are available they can be accessed like this
And, to get a unique list of role names COLLECT and DISTINCT can be used:
MATCH
(p:Project)-[r]->(s:Staff)
RETURN
COLLECT(DISTINCT type(r)) // Distinct types
Or, for properties on the relationship:
MATCH
(p:Project)-[r]->(s:Staff)
RETURN
COLLECT(DISTINCT r.role) // The "role" property if that is used
The COLLECT returns a list result and the DISTINCT keyword makes sure that there are no duplicates in the list.

Resources