I'm new to Cypher, I'm trying to learn to navigate a graph correctly.
I hava a situation like this: 2 Users have associated the same Service, the service is accessible via an Account. So, the user 'usr01' can access to the Service 'srv01' with account 'acct01'; the user 'usr02 can access to the Service 'srv01' with account 'acct02'.
The aim is to extract 2 records like this:
usr01 - srv01 - acct01
usr02 - srv01 - acct02
So, I executed these queries:
Creation of nodes:
create (s:XService {serviceId:'srv01'}) return s;
create (u:XUser {userId:'usr01'}) return u;
create (u:XUser {userId:'usr02'}) return u;
create (u:XAccount {accountId:'acct01'}) return u;
create (u:XAccount {accountId:'acct02'}) return u;
Relationship creation:
MATCH (u:XUser{userId:'usr01'}), (s:XService {serviceId:'srv01'}), (a:XAccount {accountId:'acct01'})
CREATE (u)-[:HAS_SERVICE]->(s)-[:HAS_ACCOUNT]->(a)
MATCH (u:XUser{userId:'usr02'}), (s:XService {serviceId:'srv01'}), (a:XAccount {accountId:'acct02'})
CREATE (u)-[:HAS_SERVICE]->(s)-[:HAS_ACCOUNT]->(a)
The graph result I've received is this
If I execute this query - starting from the user usr01:
MATCH (u:XUser {userId: 'usr01'}) OPTIONAL MATCH (u)-[:HAS_SERVICE]->(s:XService) OPTIONAL MATCH (s)-[:HAS_ACCOUNT]->(a:XAccount)
RETURN u.userId, s.serviceId, a.accountId;
I obtain this result:
So, how can I do to obtain the result described above (usr01 - srv01 - acct01) and not the cartesian product that I've received?
Thanks in advance
The problem is that when you add the relationship between the service and the account you do not indicate an association relationship with the user. As a solution, you can make a smart node "Access Rule":
MERGE (s:XService {serviceId:'srv01'})
MERGE (u1:XUser {userId:'usr01'})
MERGE (ua1:XAccount {accountId:'acct01'})
MERGE (u1)-[:can_access]->(ca1:AccessRule)-[:to_service]->(s)
MERGE (ca1)-[:with_account]->(ua1)
MERGE (u2:XUser {userId:'usr02'})
MERGE (ua2:XAccount {accountId:'acct02'})
MERGE (u2)-[:can_access]->(ca2:AccessRule)-[:to_service]->(s)
MERGE (ca2)-[:with_account]->(ua2)
And a query:
MATCH (u:XUser {userId: 'usr01'})
OPTIONAL MATCH ps = (u)-[:can_access]->(ca1:AccessRule)-[:to_service]->(s:XService)
OPTIONAL MATCH pa = (ca1)-[:with_account]->(a:XAccount)
RETURN u, ps, pa
Related
This is my test data:
graph = TinkerGraph.open()
g= graph.traversal()
g.addV('Account').property('id',"0x0").as('a1').
addV('Account').property('id',"0x1").as('a2').
addV('Account').property('id',"0x2").as('a3').
addV('Token').property('address','1').as('tk1').
addV('Token').property('address','2').as('tk2').
addV('Token').property('address','3').as('tk3').
addV('Trx').property('address','1').as('Trx1').
addV('Trx').property('address','1').as('Trx2').
addV('Trx').property('address','3').as('Trx3').
addE('sent').from('a1').to('Trx1').
addE('sent').from('a2').to('Trx2').
addE('received_by').from('Trx1').to('a2').
addE('received_by').from('Trx2').to('a3').
addE('distributes').from('a1').to('tk1').
addE('distributes').from('a1').to('tk2').
addE('distributes').from('a1').to('tk3').
iterate()
I need to first get all the Token addresses using the distributes relationship and then with those values loop through a traversal. This is an example of what I need for one single token
h = g.V().has('Account','id','0x0').next()
token = '1'
g.V(h).
out('sent').has('address',token).as('t1').
out('received_by').as('a2').
out('sent').has('address',token).as('t2').
out('received_by').as('a3').
select('a3','a2'). \
by('id').toList()
This is the output:
[a3:0x2,a2:0x1]
Instead of doing that has('address',token) on each hop I could omit it and just make sure the token address is the same by placing a where('t1',eq('t2')).by('address') at the end of the traversal, but this performs badly given my database design and indexes.
So what I do to iterate is:
tokens = g.V(h).out('distributes').values('address').toList()
finalList = []
for (token in tokens){
finalList.add(g.V(h).
out('sent').has('address',token).
out('received_by').as('a2').
out('sent').has('address',token).
out('received_by').as('a3').
select('a3','a2'). \
by('id').toList())
}
And this is what's stored in finalList at the end:
==>[[a3:0x2,a2:0x1]]
==>[]
==>[]
This works but I was wondering how can I iterate that token list this way without leaving Gremlin and without introducing that for loop. Also, my results contain empty results which is not optimal. The key here for me is to always be able to do that has('address',token) for each hop with the tokens that the Account node has ever sent. Thank you very much.
There is still uncertainty about what you are trying to achieve.
Nevertheless, I think this query does what you need:
g.V().has('Account', 'id', '0x0').as('a').
out('distributes').values('address').as('t').
select('a').
repeat(out('sent').where(values('address').
as('t')).
out('received_by')).
emit()
Example: https://gremlify.com/spwya4itlvd
I'm trying to export edges from grakn. I can do that with Python client like so:
edge_query = "match $c2c($c1, $c2) isa c2c; $c1 has id $id1; $c2 has id $id2;get $id1,$id2;"
with open(f"grakn.edgelist","w") as outfile:
with GraknClient(uri="localhost:48555") as client:
with client.session(keyspace=KEYSPACE) as session:
with session.transaction().read() as read_transaction:
answer_iterator = read_transaction.query(edge_query)
for answer in tqdm(answer_iterator):
id1 = answer.get("id1")
id2 = answer.get("id2")
outfile.write(f"{id1.value()} {id2.value()} \n")
Edit: For each Relation, I want to export entities pairwise. The output can be a pair of Grakn IDs.
I can ignore the attributes of relation or entities.
Exporting to edges seems like a common task. Is there a better way(more elegant, faster, more efficient) to do it in Grakn?
This works as long as the relation type c2c always has two roleplayers. However, this will produce two edges for every $c1, $c2, which is probably not what you want.
Let's take a pair of Things, with ids V123 and V456. If they satisfy $c2c($c1, $c2) isa c2c; with $c1 = V123 and $c2 = V456 then they will also satisfy the same pattern as $c1 = V456 and $c2 = V123. Grakn will return all combinations of $c1, $c2 that satisfy your query, so you'll get two answers back for this one c2c relation.
Assuming this isn't what you want, if $c1 and $c2 play different roles in the relation c2c (likely implying there is direction to the edge) then try changing the query, adding the roles, to:
edge_query = "match $c2c(role1: $c1, role2: $c2) isa c2c; $c1 has id $id1; $c2 has id $id2; get $id1,$id2;"
If they both play the same role (implying undirected edges), then we need to do something different in our logic. Either store edges as a set of sets of ids to remove duplicates without much effort, or perhaps consider using the Python ConceptAPI, something like this:
relation_query = "match $rc2c isa c2c;get;"
with open(f"grakn.edgelist","w") as outfile:
with GraknClient(uri="localhost:48555") as client:
with client.session(keyspace=KEYSPACE) as session:
with session.transaction().read() as read_transaction:
answer_iterator = read_transaction.query(relation_query)
for answer in answer_iterator:
relation_concept = answer.get("rc2c")
role_players_map = relation_concept.role_players_map()
role_player_ids = set()
for role, thing in role_players_map.items():
# Here you can do any logic regarding what things play which roles
for t in thing:
role_player_ids.add(t.id) # Note that you can retrieve a concept id from the concept object, you don't need to ask for it in the query
outfile.write(", ".join(role_player_ids) + "\n")
Of course, I have no idea what you're doing with the resulting edgelist, but for completeness, the more Grakn-esque way would be to treat the Relation as a first-class citizen since it represents a hyperedge in the Grakn knowledge model, in this case we would treat the Roles of the relation as edges. This means we aren't stuck when we have ternary or N-ary relations. We can do this by changing the query:
match $c2c($c) isa c2c; get;
Then in the result we get the id of the $c2c and of the $c.
I'm trying to copy a vertex node and retain it's relationships in ArangoDB. I'm getting a "access after data-modification" error (1579). It doesn't like it when I iterate over the source node's edges and insert an edge copy within the loop. This makes sense but I'm struggling to figure out how to do what I'm wanting within a single transaction.
var query = arangojs.aqlQuery`
let tmpNode = (FOR v IN vertices FILTER v._id == ${nodeId} RETURN v)[0]
let nodeCopy = UNSET(tmpNode, '_id', '_key', '_rev')
let nodeCopyId = (INSERT nodeCopy IN 'vertices' RETURN NEW._id)[0]
FOR e IN GRAPH_EDGES('g', ${nodeId}, {'includeData': true, 'maxDepth': 1})
let tmpEdge = UNSET(e, '_id', '_key', '_rev')
let edgeCopy = MERGE(tmpEdge, {'_from': nodeCopyId})
INSERT edgeCopy IN 'edges'
`;
This quesion is somewhat similar to 'In AQL how to re-parent a vertex' - so let me explain this in a similar way.
One should use the ArangoDB 2.8 pattern matching traversals to solve this.
We will copy Alice to become Sally with similar relations:
let alice=DOCUMENT("persons/alice")
let newSally=UNSET(MERGE(alice, {_key: "sally", name: "Sally"}), '_id')
let r=(for v,e in 1..1 ANY alice GRAPH "knows_graph"
LET me = UNSET(e, "_id", "_key", "_rev")
LET newEdge = (me._to == "persons/alice") ?
MERGE(me, {_to: "persons/sally"}) :
MERGE(me, {_from: "persons/sally"})
INSERT newEdge IN knows RETURN newEdge)
INSERT newSally IN persons RETURN newSally
We therefore first load Alice. We UNSET the properties ArangoDB should set on its own. We change the properties that have to be uniq to be uniq for Alice so we have a Sally afterwards.
Then we open a subquery to traverse ANY first level relations of Alice. In this subequery we want to copy the edges - e. We need to UNSET once more the document attributes that have to be autogenerated by ArangoDB. We need to find out which side of _from and _to pointed to Alice and relocate it to Sally.
The final insert of Sally has to be outside of the subquery, else this statement will attempt to insert one Sally per edge we traverse. We can't insert Saly in front of the query as you already found out - no subsequent fetches are allowed after the insert.
Since firebase not support any spatial indexing then I use geohash which someone advice here.
Geohash is using characters to find the nearby.
So let say I have w22qt4vhye1c w99qt4vdf4vc w22qt4vhzerct geohash store in firebase and I want to query only which geohash close to w22qt4.
How to do that?
I know firebase has startAt. I tried but not work.
UPDATE
Storing geohash to firebase
//Encode lat lng, result w22qt4vhye1c3
var geoHashLatLng = encodeGeoHash(lat,lng);
firebaseRef.child('/geohash/' + geoHashLatLng).set(id);
so in json
root
- geohash
- w22qt4vhye1c3
- id
- z99qt4vdf4vc
- w22qt4vhzerct
My question here.
I Just want to query geohash which start from characters w22qt4. Can we do that in firebase?
UPDATE 2
startAt() seems like not query with characters start with ...
Example: I have following gehash
geohash node
geohash
- a123
- a333
- b123
- c123
- d123
With the following code
var firebaseRef = new Firebase('https://test.firebaseio.com');
var query = firebaseRef.child('/geohash').startAt(null, 'a');
query.on('child_added', function(snapshot) {
console.log(snapshot.name());
});
Will get this results
startAt(null, 'a') //return a123, a333, b123, c123, d123
startAt(null, 'c123') //return c123, d123
startAt(null, 'd') //return d123
My expected results
startAt(null, 'a') //return a123, a333
startAt(null, 'c123') //return c123
startAt(null, 'd') //return d123
My guess, startAt() will query the 26 alphabet letters in sequence but not matching.
So, what can I do in firebase so I can get my expected results above?
In addition to Andrew's answer above, what you want to do to get the desired effect with geohashes (i.e. be able to do prefix queries that let you specify accuracy in the 'query', specifically, for example every 3 characters in the geohash gets you ~ +-80km) is to store the data in a more granular and discretized manner.
So, rather than how you're storing it now, you'll want to save the data by chopping the geohash string key into fragments (I've done it at the 3 character boundary, but you should choose an appropriate tokenization strategy that gives you the desired accuracy) and use each fragment as a child name, as such:
root
- geohash
- w22
- qt4
- vhy
- e1c
- w22qt4vhye1c3
- id
- vhz
- erc
- w22qt4vhzerct
- id
- z99
- qt4
- vdf
- z99qt4vdf4vc
- id
Then, as in your question, when you need to get all geohashes that start with w22qt4 you would do a query against:
firebaseRef.child('/geohash/w22/qt4/')
Which would then give you the vhy and vhz children which then have all the actual geohashes that you were interested in.
You can use a normal startAt query to find keys near the specified key. For example, in this case you'd:
var query = firebaseRef.child("geohash").startAt(null, 'c').limit(5);
query.on("child_added", ...);
This will give you the 5 elements after geoHashLatLng when sorted lexigraphically.
If you want to end your query at some specified key, you can do that as well. For example:
var query = firebaseRef.child("geohash").startAt(null, 'c').endAt('d');
Given a basic Blueprints-compatible OrientGraph with Index 'name' (unique or notunique), any suggestions for how the following could be improved, if needs be?
Note: I can't find a definitive guide to load a [blueprints] vertex using index. I have a large graph and using has('name','bob') (in console) takes 2 minutes! On the other hand, an index-based search returns in milliseconds.
The best I've come up with so far:
OrientGraph graph = new OrientGraph("local:/graph1/databases/test", "admin", "admin");
List<ODocument> resultlist = graph.getRawGraph().query(new OSQLSynchQuery<ODocument>("SELECT FROM INDEX:name WHERE KEY = 'bob'"));
ODocument resultodoc = resultlist.get(0).field("rid");
String rid = resultodoc.getIdentity().toString(); // would return something like #6:1500000
Vertex v1 = graph.getVertex(rid);
System.out.println(v1.getProperty("name"));
OrientDB supports the IndexableGraph interface. To use it take a look at:
https://github.com/tinkerpop/blueprints/wiki/Graph-Indices