Spring Boot Spring Data Java Neo4j Workshop
— Tested with Neo4j 4.0.11 —
Start a local container
docker run -p 7474:7474 -p 7687:7687 --name neo4j-apoc -e NEO4J_AUTH=neo4j/admin -e NEO4J_dbms_security_procedures_unrestricted=apoc.\* -e NEO4J_apoc_export_file_enabled=true -e NEO4J_apoc_import_file_enabled=true -e NEO4J_apoc_import_file_use__neo4j__config=true -e NEO4JLABS_PLUGINS=["apoc"] neo4j:4.0.11
Database setup
-
GraphML import
CALL apoc.import.graphml('https://raw.githubusercontent.com/calinconstantinov/neo4jworkshop/master/src/main/resources/db/sample.graphml', {readLabels: true}) -
Cypher import (schema)
Copy the one in: https://raw.githubusercontent.com/calinconstantinov/neo4jworkshop/master/src/main/resources/db/schema.cyp -
Schema: Constraints
CALL db.constraints -
Schema: Indexes
CALL db.indexes -
Schema: Meta-Graph
CALL apoc.meta.graph -
APOC Schema
CALL apoc.meta.schema -
Delete all data
MATCH (n)
DETACH DELETE n -
Delete schema
CALL apoc.schema.assert({},{})
GRAph Style Sheet customization
-
Export GRASS file
:style -
Import GRASS file
Drag and drop the one in: https://raw.githubusercontent.com/calinconstantinov/neo4jworkshop/master/src/main/resources/db/style.grass -
Reset GRASS
:style reset
Database export
Change 'file' to a locally writable file.
-
Export CSV
CALL apoc.export.csv.all('file', {}) -
Export Cypher Schema
CALL apoc.export.cypher.schema('file', {format:'plain'}) -
Export ALL Cypher
CALL apoc.export.cypher.all('file', {format:'plain'}) -
Export GraphML
CALL apoc.export.graphml.all('file', {useTypes:true})
Sample queries
-
Retrieve a user
MATCH (n:User)
WHERE n.name='Calin'
RETURN n -
Matching authentication
MATCH (n:User)-[:HAS_AUTHENTICATION]-(c)
WHERE n.name='Calin'
RETURN n, c -
Retrieving all users
MATCH (n:User)
RETURN n -
Retrieve Calin's friends
MATCH (n:User)-[:FRIENDS_WITH]-(friend)
WHERE n.name = 'Calin'
RETURN n, friend -
Retrieve all friendship pairs once
MATCH (n:User)-[:FRIENDS_WITH]->(friend)
WHERE ID(n) > ID(friend)
RETURN n, friend -
Remove duplicate friendship relationships
MATCH (u1:User)-[friendship:FRIENDS_WITH]->(u2:User) WHERE ID(u1) > ID(u2) DELETE friendship -
Retrieve friendships lists
MATCH (n:User)-[:FRIENDS_WITH]-(friend)
RETURN n.name, collect(friend.name), count(friend) AS numberOfFriends
ORDER BY numberOfFriends DESC -
Include authentication with friendships lists
MATCH (a:Authentication)<-[:HAS_AUTHENTICATION]-(n:User)-[:FRIENDS_WITH]-(friend)
RETURN n.name, count(friend) -
Optional matching authentication with friendships lists
MATCH (n:User)-[:FRIENDS_WITH]-(friend)
OPTIONAL MATCH (n)-[]-(a:Authentication)
RETURN a.email, n.name, count(friend) -
Matching Calin's posts
MATCH (n:User)
WHERE n.name = 'Calin'
WITH n
MATCH (p:Post)-[:POSTED_BY]->(n)
RETURN n, p -
Matching Calin's posts' likes
MATCH (n:User)<-[:POSTED_BY]-(p:Post)<-[like:LIKES_POST]-(liker:User)
WHERE n.name = 'Calin'
RETURN n, p, like, liker -
Matching non-narcissistic likes
MATCH (n:User)<-[:POSTED_BY]-(p:Post)<-[like:LIKES_POST]-(liker:User)
WHERE n.name = 'Calin' AND n.uuid <> liker.uuid
RETURN n.name, p.content, like.timestamp, liker.name -
Matching comments on Calin's posts
MATCH (n:User)
WHERE n.name = 'Calin'
WITH n
MATCH (commenter:User)-[:COMMENTED]-(c:Comment)-[:ON_POST]->(p:Post)-[:POSTED_BY]->(n)
RETURN n, p, c, commenter -
Now match all the way to reactions
MATCH (user:User)
WHERE user.name = 'Calin'
WITH user
MATCH (user)<-[:POSTED_BY]-(post:Post)<-[:ON_POST]-(comment:Comment)<-[:COMMENTED]-(commenter:User),
(comment)<-[:AT_COMMENT]-(reaction:Reaction)-[:OF_TYPE]->(reactionType:ReactionType),
(reaction)<-[:REACTED]-(reacter:User)
RETURN user, post, comment, commenter, reaction, reactionType, reacter -
Debugging an incorrect query
PROFILE
MATCH (user:User)
WHERE user.name = 'Calin'
WITH user
MATCH (user)<-[:POSTED_BY]-(post:Post)<-[:ON_POST]-(comment:Comment)<-[:COMMENTED]-(commenter:User),
(comment)<-[:AT_COMMENT]-(reaction:Reaction)-[:OF_TYPE]->(reactionType:ReactionType),
(r)<-[:REACTED]-(reacter:User)
RETURN user, post, comment, commenter, reaction, reactionType, reacter -
Include comments with no reactions
MATCH (user:User)
WHERE user.name = 'Calin'
WITH user
MATCH (user)<-[:POSTED_BY]-(post:Post)<-[:ON_POST]-(comment:Comment)<-[:COMMENTED]-(commenter:User) OPTIONAL MATCH (comment)<-[:AT_COMMENT]-(reaction:Reaction)-[:OF_TYPE]->(reactionType:ReactionType),
(reaction)<-[:REACTED]-(reacter:User)
RETURN user, post, comment, commenter, reaction, reactionType, reacter -
Finally match all social data related to 'Calin'
MATCH (user:User)
WHERE user.name = 'Calin'
WITH user
MATCH (user)<-[:POSTED_BY]-(post:Post)<-[:ON_POST]-(comment:Comment)<-[:COMMENTED]-(commenter:User) OPTIONAL MATCH (comment)<-[:AT_COMMENT]-(reaction:Reaction)-[:OF_TYPE]->(reactionType:ReactionType)
OPTIONAL MATCH (reaction)<-[:REACTED]-(reacter:User)
OPTIONAL MATCH (replier:User)-[:COMMENTED]->(reply:Comment)-[:REPLIED_TO]->(comment)
RETURN user, post, comment, commenter, reaction, reactionType, reacter, reply, replier -
But where do Calin's friends work?
MATCH (user:User)
WHERE user.name = 'Calin'
WITH user
MATCH (user)-[:FRIENDS_WITH]-(friend)<-[:EMPLOYED]-(company:Company)
RETURN friend.name, company.name -
Match all data
MATCH (n)
RETURN n
Handling time
-
Time data model
MATCH (y:Year)-[:HAS_MONTH]->(m:Month)-[:HAS_DAY]->(d:Day)-[:HAS_HOUR]->(h:Hour)
RETURN y, m, d, h -
Ordering time
MATCH (y:Year)-[:HAS_MONTH]->(m:Month)-[:HAS_DAY]->(d:Day)-[:HAS_HOUR]->(h:Hour)
RETURN y.uuid, m.uuid, d.uuid, h.uuid
ORDER BY y.uuid ASC, m.uuid ASC, d.uuid ASC, h.uuid ASC -
Get a sequence of hours:
MATCH (firstHour:Hour)-[:NEXT_HOUR*..5]->(h)
WHERE firstHour.uuid = '2021051522'
WITH firstHour + collect(h) as dayList
UNWIND dayList as day
RETURN day -
Get a sequence of hours including corresponding days:
MATCH (firstHour:Hour)-[:NEXT_HOUR*..5]->(h)
WHERE firstHour.uuid = '2021051522'
WITH firstHour + collect(h) as hourList
UNWIND hourList as hour
WITH hour MATCH (hour)<-[:HAS_HOUR]-(day:Day)
RETURN day, hour
Data analysis
-
Friendship relationships between companies
MATCH (c:Company)-[:EMPLOYED]->(u:User)-[:FRIENDS_WITH]-(u2:User)<-[:EMPLOYED]-(c2:Company)
WHERE c.uuid <> c2.uuid AND c.name = 'Endava'
RETURN c2.name as company, count(DISTINCT u2) as friendsOfEmployees
ORDER BY friendsOfEmployees DESC -
Computing Post Power
-
Adding specific Post Power node
MATCH (post:Post)
WITH collect(post) as posts
FOREACH (post in posts |
CREATE (pP:PostPower {postPower: 0})
MERGE (pP)<-[:HAS_POWER]-(post)) -
Computing and attaching Post Power value
MATCH (post:Post)-[:POSTED_BY]->(poster:User)
WITH post, poster
OPTIONAL MATCH (post)<-[like:LIKES_POST]-(liker:User)
WHERE poster.uuid <> liker.uuid
WITH post, poster, count(like) as likes
OPTIONAL MATCH (post)<-[:ON_POST]-(comment:Comment)<-[:COMMENTED]-(commenter:User)
WHERE poster.uuid <> commenter.uuid
WITH post, poster, likes, count(comment) as comments
OPTIONAL MATCH (post)<-[:ON_POST]-(comment)<-[:AT_COMMENT]-(reaction:Reaction)<-[:REACTED]-(reactor:User)
WHERE poster.uuid <> reactor.uuid
WITH post, poster, likes, comments, count(reaction) as reactions
OPTIONAL MATCH (post)<-[:ON_POST]-(comment)<-[:REPLIED_TO]-(reply:Comment)<-[:COMMENTED]-(replier:User)
WHERE poster.uuid <> replier.uuid
WITH post, poster, likes, comments, reactions, count(reply) as replies
WITH poster.name as posterName, post, likes, comments, reactions, replies, 1 * likes + 2 * reactions + 3 * comments + 4 * replies AS postPower
MATCH (post)-[:HAS_POWER]->(pp:PostPower) SET pp.postPower = postPower RETURN posterName, post.content, likes, comments, reactions, replies, postPower ORDER BY postPower DESC
-
-
BFFs <3
MATCH (user:User)
WHERE user.name = 'Calin'
WITH user
MATCH (user)-[:FRIENDS_WITH]-(friend)
WITH user, friend
OPTIONAL MATCH (user)<-[:POSTED_BY]-(post:Post)<-[l:LIKES_POST]-(friend) WITH user, friend, count(l) as likes
OPTIONAL MATCH (user)-[:COMMENTED]->(comment:Comment)<-[:AT_COMMENT]-(reaction:Reaction)-[:REACTED]-(friend)
WITH user, friend, likes, count(reaction) as reactions
OPTIONAL MATCH (user)<-[:POSTED_BY]-(post:Post)<-[:ON_POST]-(comment:Comment)<-[:COMMENTED]-(friend)
WITH user, friend, likes, reactions, count(comment) as comments, reactions + 2 * likes + 3 * count(comment) as friendshipPower
WHERE friendshipPower <> 0
RETURN friend.name, likes, reactions, comments, friendshipPower
ORDER BY friendshipPower DESC
Full-text indexes powered by the Apache Lucene
-
Create full-text index
CALL db.index.fulltext.createNodeIndex("user_names",["User"],["name"]); -
Term query example
CALL db.index.fulltext.queryNodes("user_names", "Calin Mihai") YIELD node, score RETURN node.name, score -
Fuzzy query example
CALL db.index.fulltext.queryNodes("user_names", "clin~") YIELD node, score RETURN node.name, score