-->

Relating an entity to a relationship proper in Neo

2019-02-26 05:13发布

问题:

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?

回答1:

@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.



回答2:

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.