Py2neo V3 adding relationships to transactions in a loop - neo4j

Trying to add nodes and relationships in transactional manner using py2neo V3.
I would like to add the person and all their movies as one transaction.
I could not get nodes in the outer and inner loops to work in the same transaction. I'm pretty confident the relationship is not being added in a transactional manner either since I'm calling tx1.graph.
Neo4j V3.0.7
Py2Neo v3.1.2
from py2neo import Graph,Node,Relationship,authenticate, watch
from py2neo.ogm import GraphObject, Property, RelatedTo, RelatedFrom
class Movie(GraphObject):
__primarykey__ = "title"
title = Property()
class Person(GraphObject):
__primarykey__ = "name"
name = Property()
acted_in = RelatedTo(Movie)
People = ["John","Jane","Tarzan"]
Movies = [["John","Movie1"],["John","Move2"],["Jane","Movie3"],["Jane","Movie4"],["Tarzan","Movie4"]]
graph = Graph("http://localhost:7474")
for p in People:
print(p)
tx = graph.begin()
p1 = Person()
p1.name = p
tx.merge(p1)
tx.commit()
for m in Movies:
if m[0] != p:
continue
print(m[1])
tx1 = graph.begin() #did not work using original tx transaction
m1 = Movie()
m1.title = m[1]
tx1.merge(m1)
p1.acted_in.add(m1)
#tx1.merge(p1) #did not create relationship
#tx1.create(p1) #did not create relationship
tx1.graph.push(p1) # worked in adding relationship, but can't guarantee was part of the transaction
tx1.commit()

Try the following loop, which uses a single transaction for each Person and its relationships:
for p in People:
print(p)
tx = graph.begin()
p1 = Person()
p1.name = p
tx.merge(p1)
for m in Movies:
if m[0] != p:
continue
print(m[1])
m1 = Movie()
m1.title = m[1]
tx.merge(m1)
p1.acted_in.add(m1)
tx.graph.push(p1)
tx.commit()
Note: It would actually be more efficient to use a single transaction for processing multiple persons. But you would not want to handle too many (depending on your data model) at one time either, as that would risk making the server run out of memory.

Related

Get count of child objects in grails Criteria query

Lets Say a domain class A has many Class B objects. I need to do a criteria query which returns
A.id
A.name
B.count(no of B elements associated with A)
B.last Updated(date of most recent update of B elements associated with A considering i have last_updated date for all B elements)
Also the query should be flexible enough to add conditions/restrictions to both A and B domain objects.
Currently I have gotten as far as this:
A.createCriteria().list {
createAlias('b','b')
projections{
property('id')
property('gender')
property('dateOfBirth')
count('b.id')
property('publicId')
}
}
But the problem is that it only returns one object and the count of child objects is for all the elements of B instead of just those associated with A
Recently I was in a similar scenario I needed a query in which one of your rows will store the count of many in a one-to-many relationship
But unlike your scenario I used native sql queries to resolve the query.
The solution was to use derived tables (I do not know how to implement them using criteria query).
In case you find it useful I share a code with the implementation taken from a grails service:
List<Map> resumeInMonth(final String monthName) {
final session = sessionFactory.currentSession
final String query = """
SELECT
t.id AS id,
e.full_name AS fullName,
t.subject AS issue,
CASE t.status
WHEN 'open' THEN 'open'
WHEN 'pending' THEN 'In progress'
WHEN 'closed' THEN 'closed'
END AS status,
CASE t.scheduled
WHEN TRUE THEN 'scheduled'
WHEN FALSE THEN 'non-scheduled'
END AS scheduled,
ifnull(d.name, '') AS device,
DATE(t.date_created) AS dateCreated,
DATE(t.last_updated) AS lastUpdated,
IFNULL(total_tasks, 0) AS tasks
FROM
tickets t
INNER JOIN
employees e ON t.employee_id = e.id
LEFT JOIN
devices d ON d.id = t.device_id
LEFT JOIN
(SELECT
ticket_id, COUNT(1) AS total_tasks
FROM
tasks
GROUP BY ticket_id) ta ON t.id = ta.ticket_id
WHERE
MONTHNAME(t.date_created) = :monthName
ORDER BY dateCreated DESC"""
final sqlQuery = session.createSQLQuery(query)
final results = sqlQuery.with {
resultTransformer = AliasToEntityMapResultTransformer.INSTANCE
setString('monthName', monthName)
list()
}
results
}
The part of interest is to declare a row within the main select and then in the clause from declare the derived query that stores the result in a row with the same name declared in the main select
SELECT ...
total_tasks --Add the count column to your select
FROM ticket t
JOIN (SELECT ticked_id, COUNT(1) as total_tasks
FROM tasks
GROUP BY ticked_id) ta ON t.id = ta.ticked_id
...rest of query
This last example I share from the answer made by the user Aaron Dietz to the question that I also formulate
I hope it is useful for you
Turns out I wasn't very far from the solution and i just needed to do grouping based on the right property which is the foreign key column in the child table which is b.a in this case so the following works now
A.createCriteria().list {
createAlias('b','b')
projections{
property('id')
property('gender')
property('dateOfBirth')
count('b.id')
groupProperty('b.a')
property('publicId')
}
}
In the criteria you need to group by the property which are not aggregate.
Try following:
A.createCriteria().list {
createAlias('b','b')
projections{
groupProperty('id','id')
groupProperty('gender','gender')
groupProperty('dateOfBirth','dateOfBirth')
count('b.id','total')
groupProperty('publicId','publicId')
}
}
or If you want to have a list of map object return you can try add resultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP)
A.createCriteria().list {
resultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP)
createAlias('b','b')
projections{
groupProperty('id','id')
groupProperty('gender','gender')
groupProperty('dateOfBirth','dateOfBirth')
count('b.id','total')
groupProperty('publicId','publicId')
}
}
Hope it can help

Cannot cast GraphObject to Node

I have a defined AbstractQuery as a Child of GraphObject :
class AbstractQuery(GraphObject):
__primarykey__ = "hash"
hash = Property()
projname = Property()
def __init__(self, hash):
self.hash = hash
self.projname = "" # TODO:initialize this
and iterate over all SQLQuery objects in my (already existing) graph and want to create a new AbstractQuery consisting of a hash. Any SQLQuery hashing to the hash determining the AbstractQuery should be connected:
def addAbstractionLayerSqlQueries(graph, logger=None):
abstractQueryTable = getAbstractQueries(graph, logger)
sqlqueries = graph.data("MATCH (n:SQLQuery) return n")
if logger is not None:
logger.info("abstracting {} queries".format(len(sqlqueries)))
counter = 1
for iterator in sqlqueries:
query = iterator['n']
if logger is not None:
logger.info("abstracting query {}/{}".format(counter,
len(sqlqueries)))
hash = sqlNormalizer.generate_normalized_query_hash(query['ts'])
if hash not in abstractQueryTable:
abstractQueryNode = al.AbstractQuery(hash)
abstractQueryTable[hash] = abstractQueryNode
graph.push(abstractQueryNode)
rel = Relationship(query, "ABSTRACTSTO", abstractQueryTable[hash])
graph.create(rel)
counter = counter + 1
Before I start this process I extract a table (using the hash as key) of all already existing AbstractQueries to prevent creating the same AbstractQuery twice.
However, when I run the method I end up with the exception:
TypeError: Cannot cast AbstractQuery to Node
Why is this and how can I fix this?
I previously entered multiple SQLQueries into my graph by using this representation:
class SQLQuery(GraphObject):
__primarykey__ = "uuid"
uuid = Property()
projname = Property()
session = Property()
user = Property()
seq = Property()
ts = Property()
sql = Property()
Statement = RelatedTo("SQLStatement")
AbstractsTo = RelatedTo("AbstractQuery")
def __init__(self, projname=None, session=None, user=None,
seq=None, ts=None, sql=None):
self.projname = projname
self.session = session
self.user = user
self.seq = seq
self.ts = ts
self.sql = sql
self.uuid = "{} [{} {}] {}.{}.{}".format(type(self).__name__,
seq, ts, projname,
session, user)
As I was able to use this representation to represent and enter Nodes I am quite flabbergasted on why py2neo rejects my AbstractQuery class as a node in my addAbstractionLayerSqlQueries function.
You're mixing two layers of API. The OGM layer sits above the regular py2neo API and a GraphObject does not directly correspond to a Node (it contains other stuff too). Therefore, you cannot build a Relationship to or from a GraphObject directly.
To access the core node behind your GraphObject, you can use my_object.__ogm__.node.
I fixed the problem by only using the Object Model and replacing the graph.data(...) query part:
def addAbstractionLayerSqlQueries(graph, logger=None):
abstractQueryTable = getAbstractQueries(graph, logger)
sqlqueries = list(adlsql.SQLQuery.select(graph))
print type(sqlqueries)
if logger is not None:
logger.info("abstracting {} queries".format(len(sqlqueries)))
counter = 1
for query in sqlqueries:
print type(query)
if logger is not None:
logger.info("abstracting query {}/{}".format(counter,
len(sqlqueries)))
hash = sqlNormalizer.generate_normalized_query_hash(query.ts)
if hash not in abstractQueryTable:
abstractQueryNode = al.AbstractQuery(hash)
abstractQueryTable[hash] = abstractQueryNode
graph.push(abstractQueryNode)
query.AbstractsTo.add(abstractQueryTable[hash])
graph.push(query)
counter = counter + 1
However, the actual reason for the error is still unknown and I'll accept any answer that explains this. This answer is only fixing the problem.

Create multiple relationships with py2neo

I want to create multiple relationships between the same node using py2neo library. I used create if the relationship does not exist and merge when it exists. Here is a sample of my code :
def create_route(graph, sourcefile, airport_nodes):
with open(sourcefile, encoding="utf8") as csvfile:
reader = csv.DictReader(csvfile)
fieldnames = reader.fieldnames
for row in reader:
source_airport = row['origin']
destination_airport = row['destination']
source_airport_node = airport_nodes[source_airport]
destination_airport_node = airport_nodes[destination_airport]
node_properties = {'distance':row['distance']}
node_properties1 = {'duration': row['duration']}
graph.create(Relationship(source_airport_node, destination_airport_node,**node_properties1))
graph.merge(Relationship(source_airport_node, destination_airport_node, **node_properties))
The problem that It get only one relationship with the last attribute which is the distance.
Thank you

Neo4J two way relationship query with cypher

I have a two-way [:FRIENDSHIP] relationship between User nodes:
(UserA)<-[:FRIENDSHIP {approved:true}]->(UserB)
Here's a little test function in Java to setup the relationship:
public void approveFriendship(User requestor, User requestee) {
Friendship friendship = new Friendship(requestor, requestee);
friendship.setApproved(true);
Friendship invertedFriendship = new Friendship(requestee, requestor);
invertedFriendship.setApproved(true);
requestor.getFriendships().add(friendship);
requestee.getFriendships().add(invertedFriendship);
userRepository.save(requestor);
userRepository.save(requestee);
}
This cypher query returns the friends of UserA, and works fine:
start user=node({0})
match (user)-[r?:FRIENDSHIP]->(friends)
where r.approved = true
return friends
This cypher query returns the posts of a friend, and does not work (returns empty result):
start n=node({0})
match (n)<-[r?:FRIENDSHIP]->(friend)<-[:AUTHOR]-(friendposts)
where r.approved = true
return friendposts order by friendposts.createdAt
When omitting the where r.approved = true line or changing it to where r.approved = false it returns posts of friends without the approved status in both cases.
Can anybody spot if I'm doing something wrong here? Much obliged.
Solved it.
Dunno why, but in a one-to-many type relationship (eg. User to Post) Neo4J prefers outgoing relationships on the Collection, and the incoming on the single entity. I had it the other way round...
Now my classes look like this:
public class User {
#RelatedTo(type = "AUTHOR")
private Set<Post> posts;
}
public class POST {
#RelatedTo(type = "AUTHOR", direction = Direction.INCOMING)
private User author;
}
And of course the cypher query has to change to reflect the new relationship direction (note arrow behind author):
start n=node({0})
match (n)<-[r?:FRIENDSHIP]->(friend)-[:AUTHOR]->(friendposts)
where r.approved = true
return friendposts order by friendposts.createdAt

How to count ocurrences in 1 to many relationship in gorm - grails

I have 2 domain classes
class A {
....
static hasMany = [bs:B]
}
class B {
int code
....
}
How can I list the number of ocurrences of all codes in B in all the A table?
Something like
b.each { thisb ->
int ocurrences = A.bs.findAll{it == thisb}.size()
...
}
Thanks
I think the reason that I'm a little confused by this question is that technically it's actually a many-to-many relationship, not really a one-to-many. Grails will create a join table ("a_b") for this relationship (because B doesn't have a belongsTo relationship to A).
The way you have your A domain constructed the hasMany relationship is a set, so B will only appear a single time in the "bs" collection. So, I believe, all you're asking is how many As have a B.
If thats true, you can use HQL to answer your question (you can also use criteria builders, but I prefer hql). Here's an example (using the build-test-data plugin to construct objects with buildLazy and adding a String name to A):
def a1 = A.buildLazy(name: "one")
def a2 = A.buildLazy(name: "two")
def a3 = A.buildLazy(name: "three")
def a4 = A.buildLazy(name: "four")
def b1 = B.buildLazy(code: 888)
def b2 = B.buildLazy(code: 999)
a1.addToBs(b1)
a2.addToBs(b1)
a3.addToBs(b1)
a4.addToBs(b1)
a1.addToBs(b2)
println "Number of As that have each B = " +
A.executeQuery("select count(b), b.code from A as a join a.bs as b group by b.code")
println "Number of As with a specific B = " +
A.executeQuery("select count(*) from A as a join a.bs as b where b = :b", [b: b1])
results in:
Number of As that have each B = [[1, 999], [4, 888]]
Number of As with a specific B = [4]

Resources