Schema design patterns

Designing a schema will vary according to the scenario of data and operations that are being used. So, once you have designed and implemented your graph model, you need to use the appropriate schema to retrieve interesting information and patterns from the graph. The tool of preference would be Cypher, as it is essentially built around graphs. Let's look at some design scenarios.

Hyper edges

The data entities can have different levels of classifications, for example, different groups contain a given user, with different roles for each group and a user being part of several groups. The user can take up various roles in multiple groups apart from the one they are a member of. The association that exists between the user, their groups, and the roles can be depicted with the help of a hyper edge. This can be implemented in a property graph model with the help of a node that captures an n-ary relationship.

Note

In mathematics, a hypergraph is a generalization of a graph in which an edge can connect any number of vertices. One edge that contains an arbitrary number of nodes is a hyper edge. Property graphs cannot natively support hyper edges. If you need a hyper edge in your model, then this can be simulated by introducing an extra node.

Hyper edges

In order to compute a user's role in a certain group (Group2 in this case), we make use of the following Cypher query for the traversal of the node with HyperEdge and calculate the results:

MATCH ({ name: 'User1' })-[:hasRoleInGroup]->(hyperEdge)-[:hasGroup]->({ name: 'Group2' }),(hyperEdge)-[:hasRole]->(role)
RETURN role.name

The result returns the role of User1 as Role1.

To calculate all the roles of a given user in the groups and display them in the form of an alphabetically sorted table, we need the traversal of the node with the HyperEdge:

MATCH ({ name: 'User1' })-[:hasRoleInGroup]->(hyperEdge)-[:hasGroup]->(group),(hyperEdge)-[:hasRole]->(role)
RETURN role.name, group.name
ORDER BY role.name ASC

The preceding query generates the following results:

role.name

group.name

"Role1"

"Group2"

"Role2"

"Group1"

Implementing linked lists

Since a graph database inherently stores data as a graph, it becomes an increasingly powerful tool to implement graph-based data structures such as linked lists and trees. For a linked list, we require the head or start node as the reference of the list. This node will have an outbound relation with the first element in the list and an inbound relation from the last element in the list. For an empty list, the reference points to itself. Such a linked list is called a circular list.

Let's initialize a linked list with no elements for which we first create a node that references itself. This is the start node used for reference; hence, it does not set a value as its property:

CREATE (start {name: 'START'})-[:POINTS]->(start)
RETURN start
Implementing linked lists

In order to add a value to it, we need to first find the relationship in which the new value should be placed. In this spot, in the place of the existing relationship, we add a new connecting node with two relationships to nodes on either ends. You also need to keep in mind that the nodes on either end can be the start node, which is the case when the list has no elements. To avoid the creation of two new value nodes, we are required to use the UNIQUE clause with the CREATE command. This can be illustrated with a Cypher query as follows:

MATCH (start)-[:POINTS*0..]->(prev),(next)-[:POINTS*0..]->(start),(prev)-[old:POINTS]->(next)
WHERE start.name = 'START' AND (prev.value < 25 OR next = start) AND (25 < next.value OR next =
  start)
CREATE UNIQUE (prev)-[:POINTS]->({ value:25 })-[:POINTS]->(next)
DELETE old
Implementing linked lists

What this clause does is that it looks for the appropriate position of the value 25 in the list and replaces that relationship with a node containing 25 connected with two new relationships.

Complex similarity computations

When you have a heavily populated graph, you can perform numerous complex computations on it and derive interesting relations in large datasets of financial organizations, stock market data, social network data, or even sports data. For example, consider finding the similarity between two players based on the frequency with which they have been eating certain food (weird, huh!):

MATCH (roger { name: 'roger' })-[rel1:EATS]->(food)<-[rel2:EATS]-(raphael)
WITH roger,count(DISTINCT rel1) AS H1,count(DISTINCT rel2) AS H2,raphael
MATCH (roger)-[rel1:EATS]->(food)<-[rel2:EATS]-(raphael)
RETURN sum((1-ABS(rel1.times/H1-rel2.times/H2))*(rel1.times+rel2.times)/(H1+H2)) AS similarity

Hence, complex computations can be carried out with minimal code involvement.

Route generation algorithms

The greatest advantage of having your data in the form of a graph is that you generate custom paths based on your requirements. For example, you need to find the common friend of two people; what you essentially have to do is find the shortest paths of length 2 using the two users and the connecting relationship between the entities. This will give us the users who are connected to the given people by a friend hop count of 1. Neo4j has a few graph de facto algorithms including those for the shortest path, which can be used in the following format:

PathFinder<Path> PathToFind = GraphAlgoFactory.shortestPath(
        Traversal.expanderForTypes( FRNDS_WITH ), 2 );
Iterable<Path> CommonPaths = PathToFind.findAllPaths( person1, person2 );

If Cypher is what appeals to you, then to return mutual friends of person1 and person2 by using traversals of the graph, you need the following snippet:

start person1 = node(3),person2 = node(24) match (person1)--(Relation)--(person2) return Relation;

If you are interested in getting all the friends of a given person person1, then all you need to do is this:

start person1 = node(3) match person1--relation--person2 where person1--person2 return relation;

Let's look at a code snippet that uses Neo4j's existing traversal algorithms on a graph modeled with weighted edges to calculate the least total weight (call this distance for now):

import org.neo4j.graphdb.Node;
import org.neo4j.kernel.Traversal;
import org.neo4j.graphalgo.PathFinder;
import org.neo4j.graphalgo.CostEvaluator;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphalgo.WeightedPath;
import org.neo4j.graphalgo.GraphAlgoFactory;
import org.neo4j.graphalgo.CommonEvaluators;
import org.neo4j.graphdb.RelationshipExpander;
/**
 * Finding shortest path (least weighted) in a graph
 */
public class DijkstraPath
{
    private final GraphServiceHelper graph;
    //Included in the code folder
    private static final String WEIGHT = "weight";
    private static final CostEvaluator<Double> evalCost;
    private static final PathFinder<WeightedPath> djktraFindPath;
    private static final RelationshipExpander relExpndr;
    
    static
    {
        // configure the path finder
        evalCost = CommonEvaluators.doubleCostEvaluator( WEIGHT );
        relExpndr = Traversal.expanderForTypes(GraphServiceHelper.MyDijkstraTypes.REL, Direction.BOTH );
        djktraFindPath = GraphAlgoFactory.dijkstra( relExpndr, evalCost );
    }

    public DijkstraPath()
    {
        graph = new GraphServiceHelper( "path_to_database" );
    }

    private void constructGraph()
    {
        graph.createRelationship( "n1", "n2", WEIGHT, 10d );
        graph.createRelationship( "n2", "n5", WEIGHT, 10d );
        graph.createRelationship( "n1", "n3", WEIGHT, 5d );
        graph.createRelationship( "n3", "n4", WEIGHT, 10d );
        graph.createRelationship( "n4", "n5", WEIGHT, 5d );
    }

    /**
     * Find the path.
     */
    private void executeDijkstraFindPath()
    {
        Node begin = graph.getNode( "n1" );
        Node endd = graph.getNode( "n5" );
        WeightedPath path = djktraFindPath.findSinglePath( begin, endd );
        for ( Node node : path.nodes() )
        {
            System.out.println( node.getProperty( GraphServiceHelper.NAME ) );
        }
    }
    /**
     * Shutdown the graphdb.
     */
    private void stop()
    {
        graph.shutdown();
    }

    /**
     * Execute the example.
     */
    public static void main( final String[] args )
    {
        DijkstraPath obj = new DijkstraPath();
        obj.constructGraph();
        obj.executeDijkstraFindPath();
        obj.stop();
    }
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset