Aggregating and Scaling multiple edges between a set of vertices in Gremlin - gremlin

I have a graph that I'm trying to filter based on the timestamp of the access edge:
I can run a query that works very well on small amounts of data to get all the accesses edges based on timestamp, and then map out A,B,C,D,E,F. I'm running into 2 problems which I'm not sure how to solve:
1- Scale (query 1 on gremlify), I can have multiple thousands of edges on the access, so say I run the query I get the 10000 edges back, along with there in/out V, even though there is a very limited set of in out in the case I can't seem to manage to dedup them without losing some data on C, D, E, F.
g.E().hasLabel('access').
has('timestamp', between(3, 5)).as('access').
outV().hasLabel('B').as('b').
inE('belongs').
as('belongs').
outV().as('c').
inE('forms').as('forms').
outV().hasLabel('D', 'E', 'F').
as('element').
select('access').
inV().as('a').
select(
'a',
'access',
'b',
'belongs',
'c',
'forms',
'element'
).by(valueMap(true))
2- Aggregate (query 2 on gremlify), on the access edge I have a property "outcome" which can have multiple values. I've tried aggregating the outcome between A and C, (there could be a lot of B's but I don't need them most of the time) but when I do that it seems to take the full aggregate of the graph, not just between those 2 vertices
g.E().hasLabel('access').
has('timestamp', between(3, 5)).as('access').
outV().hasLabel('B').as('b').
inE('belongs').
as('belongs').
outV().as('c').
inE('forms').as('forms').
outV().hasLabel('D', 'E', 'F').
as('element').
select('access').
inV().as('a').
project(
'a',
'belongs',
'c',
'forms',
'element',
'outcome'
).by(coalesce(
select('a').
label(),
constant('default')
)).
by(coalesce(
select('belongs').
label(),
constant('default')
)).
by(coalesce(
select('c').
label(),
constant('default')
)).
by(coalesce(
select('forms').
label(),
constant('default')
)).
by(coalesce(
select('element').
label(),
constant('default')
)).
by(select('access').
groupCount().
by(select('access').
values('outcome')))
in this case it always returns nok value for all edges, and the value is always 1 (number of edges basically, not the aggregate of edges between vertices)
{
"nok": 1
}
Sandbox:
https://gremlify.com/1bos0bj1h03i/4
I'm pretty sure I'm missing something is my understanding of tinkerpop, any pointers would be great!

As to part 1, you can try and build your query starting with:
g.V().hasLabel('A').local(__.inE('access').order.by('timestamp', desc).limit(1))
As to part 2, you have not stated explicitly what you want to aggregate and how (implicitly: groupCount). It better be a separate question on SO. Probably you need a count() step to count property values on a single vertex and a sack() step to aggregate them along the traversal.

What was happening (I think, this is based on observation and limited knowledge of the gremlin engine). Each step would try to look at every edge in the graph (20k in my test env), so the goal was to try and reduce as much as possible the starting node set.
Another think I was trying to do is to do projects inside group(), but it seems that it still processes all the edge until you get out of the group. I'm not sure how that works exactly, but running the projects after the groups reduces the 20k traversals to 75.
What I ended up doing is:
querying all "A" and filtering by label
then applying a filter to reduce the set (filter(has('timestamp', between(1, 2))))
then grouping access edges by "a" node, then counting the outcome fields to create my aggregate:
group().
by(select('a')).
by(
group().
by(outV().in('belongs')).
by(groupCount().
by(values('outcome'))))
When doing this is significantly reduced the number of edges being traversed.
From there I made my projections etc... to format my response.
That moved the query time from:
>TOTAL - - 91957.436 -
To:
>TOTAL - - 960.994 -
Playground with working code: https://gremlify.com/8pifxw2uws5/1

Related

Repeat in gremlin

Two queries related to gremlin are as follows:
Want to stop the traversal when a condition is satisfied during repeated condition check.
g.V().has('label_','A')).emit().repeat(inE().outV()).until(has('stop',1)).project('depth','values').by(valueMap('label_','stop'))
I want the query to stop returning further values when the stop is equal to 1 for the node encountered during the repeat statement. But the query doesn't stop and return all the records.
Output required:
=>{label_='A',stop=0}
=>{label_='B',stop=0}
=>{label_='C',stop=1}
Query to return traversal values in the following format considering if edge exists between them. Considering the graph as A->E1->B->E2->C. The output must be as follows
=> A,E1,B
=> B,E2,C
A, B, C, E1, E2 represents properties respectively where is the starting node
For the first part, it seems you traversing on the in edges and not on the out is this on purpose? if so replace the out() in the repeat to in
g.V().has(label, 'A').emit().
repeat(out()).until(has('stop', 1)).
project('label', 'stop').
by(label).
by(values('stop'))
example: https://gremlify.com/ma2xkkszkzr/1
for the second part, I'm still not sure what you meant if you just want to get all edges with their out and in you can use elementMap:
g.E().elementMap()
example: https://gremlify.com/ma2xkkszkzr/4
and if not supported you can maybe do something like this:
g.E().local(union(
outV(),
identity(),
inV()
).label().fold())
example: https://gremlify.com/ma2xkkszkzr/2

How do I produce output even when there is no edge and when using select for projection

Can someone help me please with this simple query...Many thanks in advance...
I am using the following gremlin query and it works well giving me the original vertex (v) (with id-=12345), its edges (e) and the child vertex (id property). However, say if the original vertex 'v' (with id-12345) has no outgoing edges, the query returns nothing. I still want the properties of the original vertex ('v') even if it has no outgoing edges and a child. How can I do that?
g.V().has('id', '12345').as('v').
outE().as('e').
inV().
as('child_v').
select('v', 'e', 'child_v').
by(valueMap()).by(id).by(id)
There are a couple of things going on here but the major update you need to the traversal is to use a project() step instead of a select().
select() and project() steps are similar in that they both allow you to format the results of a traversal however they differ in (at least) one significant way. select() steps function by allowing you to access previously traversed and labeled elements (via as). project() steps allow you take the current traverser and branch it to manipulate the output moving forward.
In your original traversal, when there are no outgoing edges from original v so all the traversers are filtered out during the outE() step. Since there are no further traversers after the outE() step then remainder of the traversal has no input stream so there is no data to return. If you use a project() step after the original v you're able to return the original traverser as well as return the edges and incident vertex. This does lead to a slight complication when handling cases where no out edges exist. Gremlin does not handle null values, such as no out edges existing, you need to return some constant value for these statements using a coalesce statement.
Here is functioning version of this traversal:
g.V().hasId(3).
project('v', 'e', 'child_v').
by(valueMap()).
by(coalesce(outE().id(), constant(''))).
by(coalesce(out().id(), constant('')))
Currently you will get a lot of duplicate data, in the above query you will get the vertex properties E times. probably will be better to use project:
g.V('12345').project('v', 'children').
by(valueMap()).
by(outE().as('e').
inV().as('child').
select('e', 'child').by(id).fold())
example: https://gremlify.com/a1
You can get the original data format if you do something like this:
g.V('12345').as('v').
coalesce(
outE().as('e').
inV().
as('child_v')
select('v', 'e', 'child_v').
by(valueMap()).by(id).by(id),
project('v').by(valueMap())
)
example: https://gremlify.com/a2

Add edge if not exist using gremlin

I'm using cosmos graph db in azure.
Does anyone know if there is a way to add an edge between two vertex only if it doesn't exist (using gremlin graph query)?
I can do that when adding a vertex, but not with edges. I took the code to do so from here:
g.Inject(0).coalesce(__.V().has('id', 'idOne'), addV('User').property('id', 'idOne'))
Thanks!
It is possible to do with edges. The pattern is conceptually the same as vertices and centers around coalesce(). Using the "modern" TinkerPop toy graph to demonstrate:
gremlin> g.V().has('person','name','vadas').as('v').
V().has('software','name','ripple').
coalesce(__.inE('created').where(outV().as('v')),
addE('created').from('v').property('weight',0.5))
==>e[13][2-created->5]
Here we add an edge between "vadas" and "ripple" but only if it doesn't exist already. the key here is the check in the first argument to coalesce().
The performance of the accepted answer isn't great since it use inE(...), which is an expensive operation.
This query is what I use for my work in CosmosDB:
g.E(edgeId).
fold().
coalesce(
unfold(),
g.V(sourceId).
has('pk', sourcePk).
as('source').
V(destinationId).
has('pk', destinationPk).
addE(edgeLabel).
from('source').
property(T.id, edgeId)
)
This uses the id and partition keys of each vertex for cheap lookups.
I have been working on similar issues, trying to avoid duplication of vertices or edges. The first is a rough example of how I check to make sure I am not duplicating a vertex:
"g.V().has('word', 'name', '%s').fold()"
".coalesce(unfold(),"
"addV('word')"
".property('name', '%s')"
".property('pos', '%s')"
".property('pk', 'pk'))"
% (re.escape(category_),re.escape(category_), re.escape(pos_))
The second one is the way I can make sure that isn't a directional edge in either direction. I make use of two coalesce statements, one nested inside the other:
"x = g.V().has('word', 'name', '%s').next()\n"
"y = g.V().has('word', 'name', '%s').next()\n"
"g.V(y).bothE('distance').has('weight', %f).fold()"
".coalesce("
"unfold(),"
"g.addE('distance').from(x).to(y).property('weight', %f)"
")"
% (word_1, word_2, weight, weight)
So, if the edge exists y -> x, it skips producing another one. If y -> x doesn't exist, then it tests to see if x -> y exists. If not, then it goes to the final option of creating x -> y
Let me know if anyone here knows of a more concise solution. I am still very new to gremlin, and would love a cleaner answer. Though, this one appears to suffice.
When I implemented the previous solutions provided, when I ran my code twice, it produced an edge for each try, because it only tests one direction before creating a new edge.

Choose between 2 available edges during traversal

I'm relatively new to Gremlin, and the company I'm with is looking to implement a graph database with some temporary edges within it. Each vertex could have 1 or more edge, and each edge would have a property on it that is essentially isTemporary true/false.
When traversing the graph, if "isTemporary" = true we should follow that edge, otherwise we should follow the edge where "isTemporary" = false.
I.e.,
A-[isTemporary:true, value 1] -> B
A-[isTemporary:false, value 2] -> C
B-[isTemporary: false, value 3] -> D
Running a single gremlin query should return A->B->D in this case.
I've looked through TinkerPop3 documentation, and it seems like "choose" may be what I want to use here, but all the examples seem to return a value, when what I want is a traversal to come back so I can repeatedly act on the traversal.
Any help would be appreciated.
You could be looking for the coalesce step.
Considering this graph:
g.addV().as('a').property('name', 'A').
addV().as('b').property('name', 'B').
addV().as('c').property('name', 'C').
addV().as('d').property('name', 'D').
addE('someLink').from('a').to('b').
property('isTemporary', true).property('value', 1).
addE('someLink').from('a').to('c').
property('isTemporary', false).property('value', 2).
addE('someLink').from('b').to('d').
property('isTemporary', false).property('value', 3)
The following query will return all paths from A to D, attempting to traverse via isTemporary: true edges if present, or via isTemporary: false edges otherwise (coalesce step), iteratively.
g.V().has('name', 'A').
repeat(
coalesce(
outE().has('isTemporary', true).inV(),
outE().has('isTemporary', false).inV()
)
).
until(has('name', 'D')).
path().by('name')
Result:
==>[A,B,D]

Traverse Graph With Directed Cycles using Relationship Properties as Filters

I have a Neo4j graph with directed cycles. I have had no issue finding all descendants of A assuming I don't care about loops using this Cypher query:
match (n:TEST{name:"A"})-[r:MOVEMENT*]->(m:TEST)
return n,m,last(r).movement_time
The relationships between my nodes have a timestamp property on them, movement_time. I've simulated that in my test data below using numbers that I've imported as floats. I would like to traverse the graph using the timestamp as a constraint. Only follow relationships that have a greater movement_time than the movement_time of the relationship that brought us to this node.
Here is the CSV sample data:
from,to,movement_time
A,B,0
B,C,1
B,D,1
B,E,1
B,X,2
E,A,3
Z,B,5
C,X,6
X,A,7
D,A,7
Here is what the graph looks like:
I would like to calculate the descendants of every node in the graph and include the timestamp from the last relationship using Cypher; so I'd like my output data to look something like this:
Node:[{Descendant,Movement Time},...]
A:[{B,0},{C,1},{D,1},{E,1},{X,2}]
B:[{C,1},{D,1},{E,1},{X,2},{A,7}]
C:[{X,6},{A,7}]
D:[{A,7}]
E:[{A,3}]
X:[{A,7}]
Z:[{B,5}]
This non-Neo4J implementation looks similar to what I'm trying to do: Cycle enumeration of a directed graph with multi edges
This one is not 100% what you want, but very close:
MATCH (n:TEST)-[r:MOVEMENT*]->(m:TEST)
WITH n, m, r, [x IN range(0,length(r)-2) |
(r[x+1]).movement_time - (r[x]).movement_time] AS deltas
WHERE ALL (x IN deltas WHERE x>0)
RETURN n, collect(m), collect(last(r).movement_time)
ORDER BY n.name
We basically find all the paths between any of your nodes (beware cartesian products get very expensive on non-trivial datasets). In the WITH we're building a collection delta's that holds the difference between two subsequent movement_time properties.
The WHERE applies an ALL predicate to filter out those having any non-positive value - aka we guarantee increasing values of movement_time along the path.
The RETURN then just assembles the results - but not as a map, instead one collection for the reachable nodes and the last value of movement_time.
The current issue is that we have duplicates since e.g. there are multiple paths from B to A.
As a general notice: this problem is much more elegantly and more performant solvable by using Java traversal API (http://neo4j.com/docs/stable/tutorial-traversal.html). Here you would have a PathExpander that skips paths with decreasing movement_time early instead of collection all and filter out (as Cypher does).

Resources