In neo4j, a query to count the number of distinct structures - neo4j

In neo4j my database consists of chains of nodes. For each distinct stucture/layout (does graph theory has a better word?), I want to count the number of chains. For example, the database consists of 9 nodes and 5 relationships as this:
(:a)->(:b)
(:b)->(:a)
(:a)->(:b)
(:a)->(:b)->(:b)
where (:a) is a node with label a. Properties on nodes and relationships are irrelevant.
The result of the counting should be:
------------------------
| Structure | n |
------------------------
| (:a)->(:b) | 2 |
| (:b)->(:a) | 1 |
| (:a)->(:b)->(:b) | 1 |
------------------------
Is there a query that can achieve this?
Appendix
Query to create test data:
create (:a)-[:r]->(:b), (:b)-[:r]->(:a), (:a)-[:r]->(:b), (:a)-[:r]->(:b)-[:r]->(:b)

EDIT:
Thanks for the clarification.
We can get the equivalent of what you want, a capture of the path pattern using the labels present:
MATCH path = (start)-[*]->(end)
WHERE NOT ()-->(start) and NOT (end)-->()
RETURN [node in nodes(path) | labels(node)[0]] as structure, count(path) as n
This will give you a list of the labels of the nodes (the first label present for each...remember that nodes can be multi-labeled, which may throw off your results).
As for getting it into that exact format in your example, that's a different thing. We could do this with some text functions in APOC Procedures, specifically apoc.text.join().
We would need to first add formatting around the extraction of the first label to add the prefixed : as well as the parenthesis. Then we could use apoc.text.join() to get a string where the nodes are joined by your desired '->' symbol:
MATCH path = (start)-[*]->(end)
WHERE NOT ()-->(start) and NOT (end)-->()
WITH [node in nodes(path) | labels(node)[0]] as structure, count(path) as n
RETURN apoc.text.join([label in structure | '(:' + label + ')'], '->') as structure, n

Related

Performance of the transitive closure query in Neo4j

I am trying to compute the transitive closure of an undirected graph in Neo4j using the following Cypher Query ("E" is the label that every edge of the graph has):
MATCH (a) -[:E*]- (b) WHERE ID(a) < ID(b) RETURN DISTINCT a, b
I tried to execute this query on a graph with 10k nodes and around 150k edges, but even after 8 hours it did not finish. I find this surprising, because even the most naive SQL solutions are much faster and I expected that Neo4j would be much more efficient for these kind of standard graph queries. So is there something that I am missing, maybe some tuning of the Neo4j server or a better way to write the query?
Edit
Here is the result of EXPLAINing the above query:
+--------------------------------------------+
| No data returned, and nothing was changed. |
+--------------------------------------------+
908 ms
Compiler CYPHER 3.3
Planner COST
Runtime INTERPRETED
+-----------------------+----------------+------------------+--------------------------------+
| Operator | Estimated Rows | Variables | Other |
+-----------------------+----------------+------------------+--------------------------------+
| +ProduceResults | 14069 | a, b | |
| | +----------------+------------------+--------------------------------+
| +Distinct | 14069 | a, b | a, b |
| | +----------------+------------------+--------------------------------+
| +Filter | 14809 | anon[11], a, b | ID(a) < ID(b) |
| | +----------------+------------------+--------------------------------+
| +VarLengthExpand(All) | 49364 | anon[11], b -- a | (a)-[:E*]-(b) |
| | +----------------+------------------+--------------------------------+
| +AllNodesScan | 40012 | a | |
+-----------------------+----------------+------------------+--------------------------------+
Total database accesses: ?
You can limit the direction, but it requires the graph to be directed.
After doing some testing and profiling of my own, I found that for even very small sets of data (Randomly-generated sets of 10 nodes with 2 random edges on each), making the query be only for a single direction cut down on database hits by a factor of 10000 (from 2266909 to 149 database hits).
Adding a direction to your query (and thus forcing the graph to be directed) cuts down the search space by a great deal, but it requires the graph to be directed.
I also tried simply adding a reverse relationship for each directed one, to see if that would have similar performance. It did not; it did not complete before 5 minutes had passed, at which point I killed it.
Unfortunately, you are not doing anything wrong, but your query is massive.
Neo4J being a graph database does not mean that all mathematical operations involving graphs will be extremely fast; they are still subject to performance constraints, up to and including the transitive closure operation.
The query you have written is an unbounded path search for every single pair of nodes. The node pairs are bounded, but not in a very meaningful way (the bound of ID(a) < ID(b) just means that the search only needs to be done one way; there are still 10k! (as in factorial) possible sets of nodes in the result set.
And then, that's only after every single path is checked. Searching for the entire transitive closure of a graph the size that you specified will be extremely expensive performance-wise.
The SQL that you posted is not performing the same operation.
You mentioned in the comments that you tried this query in a relational table in a recursive form:
WITH RECURSIVE temp_tc AS (
SELECT v AS a, v AS b FROM nodes
UNION SELECT a,b FROM edges g
UNION SELECT t.a,g.b FROM temp_tc t, edges g WHERE t.b = g.a
)
SELECT a, b FROM temp_tc;
I should note that this query is not performing the same thing that Neo4J does when it tries to find all paths. Before Neo4J can start to pare down your results, it must generate a result set that consists of every single path in the entire graph.
The SQL and relational query does not do that; it starts from the list of links, but that recursive query has the effect of removing any potential duplicate links; it discovers other links as its searching for the links of others; e.g. if the graph looks like (A)-(B)-(C), that query will find that B connects to C in the process of finding that A connects to C.
With the Neo4J, every path must be discovered separately.
If this is your general use-case, it is possible that Neo4J is not a good choice if speed is a concern.

Get all node-relation originating from a node in Neo4J

Let's say we have this cypher
match (n:Person{pid:322})-[k*1..2]->(s) return k
k would be a Collection of all relation originating from a specific node n in at most 2 hops.
How can I get all Node-Relation-Node where Relation is in k? It's something like match (a)-[b]->(c) foreach b in k return a,b,c but I know this query is invalid since foreach keyword in Neo4J can't be used in this case.
Edit:
I think I should add some illustration to make things clearer. Here I use an example from Neo4J documentation:
When I start from Charlie Sheen and using 1..2 range, the query must return (assuming the query ends with return a,b,c)
a | b | c
Charlie Sheen | ACTED_IN | Wall Street
Charlie Sheen | FATHER | Martin Sheen
Martin Sheen | ACTED_IN | The American President
Martin Sheen | ACTED_IN | Wall Street
This query should produce the a, b, and c values your edited question asks for (assuming you always want the name property values):
MATCH (n:Person{pid:322})-[k*1..2]->(s)
WITH LAST(k) AS lk
RETURN STARTNODE(lk).name AS a, TYPE(lk) AS b, ENDNODE(lk).name AS c;
I'm not sure why that wouldn't just be:
match (n:Person{pid:322})-[k*1..2]->(s) return n,k,s
Unless I'm misunderstanding your question.
A path will give you the sequence of nodes and relationsjips from n to s via k
MATCH p=(n:Person{pid:322})-[k*1..2]->(s) return p
You can also extract only the nodes and/or only the relationships from the path using nodes(p) or rels(p)

Do labels order effects search time?

I'm using neo4j 2.1.7 Recently i was experimenting with Match queries, searching for nodes with several labels. And i found out, that generally query
Match (p:A:B) return count(p) as number
and
Match (p:B:A) return count(p) as number
works different time, extremely in cases when you have for example 2 millions of Nodes A and 0 of Nodes B.
So do labels order effects search time? Is this future is documented anywhere?
Neo4j internally maintains a labelscan store - that's basically a lookup to quickly get all nodes carrying a definied label A.
When doing a query like
MATCH (n:A:B) return count(n)
labelscanstore is used to find all A nodes and then they're filtered if those nodes carry label B as well. If n(A) >> n(B) it's way more efficient to do MATCH (n:B:A) instead since you look up only a few B nodes and filter those for A.
You can use PROFILE MATCH (n:A:B) return count(n) to see the query plan. For Neo4j <= 2.1.x you'll see a different query plan depending on the order of the labels you've specified.
Starting with Neo4j 2.2 (milestone M03 available as of writing this reply) there's a cost based Cypher optimizer. Now Cypher is aware of node statistics and they are used to optimize the query.
As an example I've used the following statements to create some test data:
create (:A:B);
with 1 as a foreach (x in range(0,1000000) | create (:A));
with 1 as a foreach (x in range(0,100) | create (:B));
We have now 100 B nodes, 1M A nodes and 1 AB node. In 2.2 the two statements:
MATCH (n:B:A) return count(n)
MATCH (n:A:B) return count(n)
result in the exact same query plan (and therefore in the same execution speed):
+------------------+---------------+------+--------+-------------+---------------+
| Operator | EstimatedRows | Rows | DbHits | Identifiers | Other |
+------------------+---------------+------+--------+-------------+---------------+
| EagerAggregation | 3 | 1 | 0 | count(n) | |
| Filter | 12 | 1 | 12 | n | hasLabel(n:A) |
| NodeByLabelScan | 12 | 12 | 13 | n | :B |
+------------------+---------------+------+--------+-------------+---------------+
Since there are only few B nodes, it's cheaper to scan for B's and filter for A. Smart Cypher, isn't it ;-)

Express abscence of edges in Cypher

how do I express the following in Cypher
"Return all nodes with at least one incoming edge of type A and no outgoing edges".
Best Regards
You can use a pattern to exclude nodes from the result subset like this:
MATCH ()-[:A]->(n) WHERE NOT (n)-->() RETURN n
Try
MATCH (n)
WHERE ()-[:A]->n AND NOT n-->()
RETURN n
or
MATCH ()-[:A]->(n)
WHERE NOT n-->()
RETURN DISTINCT n
Edit
Pattern expressions can be used both for pattern matching and as predicates for filtering. If used in the MATCH clause, the paths that answer the pattern are included in the result. If used for filtering, in the WHERE clause, the pattern serves as a limiting condition on the paths that have previously been matched. The result is limited, not extended to include the filter condition. When a pattern is used as a predicate for filtering, the negation of that predicate is also a predicate that can be used as a filter condition. No path answers to the negation of a pattern (if there is such a thing) so negations of patterns cannot be used in the MATCH clause. The phrase
Return all nodes with at least one incoming edge of type A and no outgoing edges
involves two patterns on nodes n, namely any incoming relationship [:A] on n and any outgoing relationship on n. The second must be interpreted as a pattern for a predicate filter condition since it involves a negation, not any outgoing relationship on n. The first, however, can be interpreted either as a pattern to match along with n, or as another pattern predicate filter condition.
These two interpretations give rise to the two cypher queries above. The first query matches all nodes and uses both patterns to filter the result. The second matches the incoming relationship on n along with n and uses the second pattern to filter the results.
The first query will match every node only once before the filtering happens. It will therefore return one result item per node that meets the criteria. The second query will match the pattern any incoming relationship [:A] on n once for each path, i.e. once for each incoming relationship on n. It may therefore contain a node multiple times in the result, hence the DISTINCT keyword to remove doubles.
If the items of interest are precisely the nodes, then using both patterns for predicates in the WHERE clause seems to me the correct interpretation. It is also more efficient since it needs to find only zero or one incoming [:A] on n to resolve the predicate. If the incoming relationships are also of interest, then some version of the second query is the right choice. One would need to bind the relationship and do something useful with it, such as return it.
Below are the execution plans for the two queries executed on a 'fresh' neo4j console.
First query:
----
Filter
|
+AllNodes
+----------+------+--------+-------------+------------------------------------------------------------------------------------------------------------------------------+
| Operator | Rows | DbHits | Identifiers | Other |
+----------+------+--------+-------------+------------------------------------------------------------------------------------------------------------------------------+
| Filter | 0 | 0 | | (nonEmpty(PathExpression((17)-[ UNNAMED18:A]->(n), true)) AND NOT(nonEmpty(PathExpression((n)-[ UNNAMED36]->(40), true)))) |
| AllNodes | 6 | 7 | n, n | |
+----------+------+--------+-------------+------------------------------------------------------------------------------------------------------------------------------+
Second query:
----
Distinct
|
+Filter
|
+TraversalMatcher
+------------------+------+--------+-------------+--------------------------------------------------------------+
| Operator | Rows | DbHits | Identifiers | Other |
+------------------+------+--------+-------------+--------------------------------------------------------------+
| Distinct | 0 | 0 | | |
| Filter | 0 | 0 | | NOT(nonEmpty(PathExpression((n)-[ UNNAMED30]->(34), true))) |
| TraversalMatcher | 0 | 13 | | n, UNNAMED8, n |
+------------------+------+--------+-------------+--------------------------------------------------------------+

Get Node ID's in Neo4j using Python

I have recently begun using Neo4j and am struggling to understand how things work. I am trying to create relationships between nodes that I created earlier in my script. The cypher query that I found looks like it should work, but I don't know how to get the id's to replace the #'s
START a= node(#), b= node(#)
CREATE UNIQUE a-[r:POSTED]->b
RETURN r
If you want to use plain cypher, the documentation has a lot of usage examples.
When you create nodes you can return them (or just their ids by returning id(a)), like this:
CREATE (a {name:'john doe'}) RETURN a
This way you can keep the id around to add relationships.
If you want to attach relationships later, you should not use the internal id of the nodes to reference them from external system. They can for example be re-used if you delete and create nodes.
You can either search for a node by scanning over all and filtering using WHERE or add an index to your database, e.g. if you add an auto_index on name:
START n = node:node_auto_index(name='john doe')
and continue from there. Neo4j 2.0 will support index lookup transparently so that MATCH and WHERE should be as efficient.
If you are using python, you can also take a look at py2neo which provides you with a more pythonic interface while using cypher and the REST interface to communicate with the server.
This could be what you are looking for:
START n = node(*) , x = node(*)
Where x<>n
CREATE UNIQUE n-[r:POSTED]->x
RETURN r
It will create POSTED relationship between all the nodes like this
+-----------------------+
| r |
+-----------------------+
| (0)-[10:POSTED]->(1) |
| (0)-[10:POSTED]->(2) |
| (0)-[10:POSTED]->(3) |
| (1)-[10:POSTED]->(0) |
| (1)-[10:POSTED]->(2) |
| (1)-[10:POSTED]->(3) |
| (2)-[10:POSTED]->(0) |
| (2)-[10:POSTED]->(1) |
| (2)-[10:POSTED]->(3) |
| (3)-[10:POSTED]->(0) |
| (3)-[10:POSTED]->(1) |
| (3)-[10:POSTED]->(2) |
And if you don't want a relation between the reference node(0) and the other nodes, you can make the query like this
START n = node(*), x = node(*)
WHERE x<>n AND id(n)<>0 AND id(x)<>0
CREATE UNIQUE n-[r:POSTED]->x
RETURN r
and the result will be like that:
+-----------------------+
| r |
+-----------------------+
| (1)-[10:POSTED]->(2) |
| (1)-[10:POSTED]->(3) |
| (2)-[10:POSTED]->(1) |
| (2)-[10:POSTED]->(3) |
| (3)-[10:POSTED]->(1) |
| (3)-[10:POSTED]->(2) |
On the client side using Javascript I post the cypher query:
start n = node(*) WHERE n.name = '" + a.name + "' return n
and then parse the id number from response "self" in the form of:
server_url:7474/db/data/node/node_id
After hours of trying to figure this out, I finally found what I was looking for. I was struggling with how nodes were getting returned and found that
userId=person[0][0][0].id
would return what I wanted. Thanks for all your help though!
Using py2neo, the way I've found that is really useful is to use the remote module.
from py2neo import Graph, remote
graph = Graph()
graph.run('CREATE (a)-[r:POSTED]-(b)')
a = graph.run('MATCH (a)-[r]-(b) RETURN a').evaluate()
a_id = remote(a)._id
b = graph.run('MATCH (a)-[r]-(b) WHERE ID(a) = {num} RETURN b', num=a_id).evaluate()
b_id = remote(b)._id
graph.run('MATCH (a)-[r]-(b) WHERE ID(a)={num1} AND ID(b)={num2} CREATE (a)-[x:UPDATED]-(b)', num1=a_id, num2=b_id)
The remote function takes in a py2neo Node object and has an _id attribute that you can use to return the current ID number from the graph database.

Resources